converted project into a python package
This commit is contained in:
parent
134043ceb2
commit
9abb009c4c
6
.gitignore
vendored
6
.gitignore
vendored
@ -3,13 +3,15 @@
|
||||
# Project-related files
|
||||
|
||||
# Run logs
|
||||
backend/run_logs/*
|
||||
screenshottocode/run_logs/*
|
||||
|
||||
# Weird Docker setup related files
|
||||
backend/backend/*
|
||||
screenshottocode/screenshottocode/*
|
||||
|
||||
# Env vars
|
||||
frontend/dist/*
|
||||
frontend/.env.local
|
||||
screenshottocode/.env
|
||||
.env
|
||||
|
||||
# Mac files
|
||||
|
||||
41
README.md
41
README.md
@ -28,41 +28,29 @@ See the [Examples](#-examples) section below for more demos.
|
||||
|
||||
The app has a React/Vite frontend and a FastAPI backend. You will need an OpenAI API key with access to the GPT-4 Vision API or Google API key for Gemini Pro Vision.
|
||||
|
||||
## Installation
|
||||
Install package with your preferred python package management app.
|
||||
|
||||
```bash
|
||||
pip install https://github.com/theonlyamos/screenshot-to-code/archive/v0.1.0.zip
|
||||
```
|
||||
|
||||
## Usage
|
||||
After installation, you can run the package from the terminal/command prompt and access the app on http://localhost:7001.
|
||||
Model Configuration can also be found on the settings modal of the web ui.
|
||||
|
||||
Run the backend (I use Poetry for package management - `pip install poetry` if you don't have it):
|
||||
|
||||
### Run with GPT-4 Vision
|
||||
You can run this with GPT-4 Vision by using the following command line:
|
||||
```bash
|
||||
cd backend
|
||||
echo "OPENAI_API_KEY=sk-your-key" > .env
|
||||
poetry install
|
||||
poetry shell
|
||||
poetry run uvicorn main:app --reload --port 7001
|
||||
screenshottocode --api-key "<Your Api Key>"
|
||||
```
|
||||
|
||||
### Run with Gemini Pro Vision
|
||||
You can also run this with Gemini Pro Vision by using the following command line instead:
|
||||
```bash
|
||||
cd backend
|
||||
echo "GOOGLE_API_KEY=sk-your-key" >.env
|
||||
poetry install
|
||||
poetry shell
|
||||
MODEL=gemini poetry run uvicorn main:app --reload --port 7001
|
||||
screenshottocode --model gemini --api-key "<Your Api Key>"
|
||||
```
|
||||
|
||||
|
||||
Run the frontend:
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
yarn
|
||||
yarn dev
|
||||
```
|
||||
|
||||
Open http://localhost:5173 to use the app.
|
||||
|
||||
If you prefer to run the backend on a different port, update VITE_WS_BACKEND_URL in `frontend/.env.local`
|
||||
Using Gemini Pro Vision model is a lot slower than using GPT-4 Vision since Gemini has a maximum output token of 2048. This means the results are fetched iteratively.
|
||||
|
||||
For debugging purposes, if you don't want to waste GPT4-Vision credits, you can run the backend in mock mode (which streams a pre-recorded response):
|
||||
|
||||
@ -76,7 +64,8 @@ MOCK=true poetry run uvicorn main:app --reload --port 7001
|
||||
|
||||
## Docker
|
||||
|
||||
If you have Docker installed on your system, in the root directory, run:
|
||||
If you have Docker installed on your system, in the root directory,
|
||||
clone the reposition and then run:
|
||||
|
||||
```bash
|
||||
echo "OPENAI_API_KEY=sk-your-key" > .env
|
||||
|
||||
156
backend/.gitignore
vendored
156
backend/.gitignore
vendored
@ -1,156 +0,0 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintainted in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
|
||||
# Temporary eval output
|
||||
evals
|
||||
@ -1,21 +0,0 @@
|
||||
FROM python:3.12-slim-bullseye
|
||||
|
||||
ENV POETRY_VERSION 1.4.1
|
||||
|
||||
# Install system dependencies
|
||||
RUN pip install "poetry==$POETRY_VERSION"
|
||||
|
||||
# Set work directory
|
||||
WORKDIR /app
|
||||
|
||||
# Copy only requirements to cache them in docker layer
|
||||
COPY poetry.lock pyproject.toml /app/
|
||||
|
||||
# Disable the creation of virtual environments
|
||||
RUN poetry config virtualenvs.create false
|
||||
|
||||
# Install dependencies
|
||||
RUN poetry install
|
||||
|
||||
# Copy the current directory contents into the container at /app
|
||||
COPY ./ /app/
|
||||
@ -1,3 +0,0 @@
|
||||
# Run tests
|
||||
|
||||
poetry run pytest
|
||||
@ -1,21 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import httpx
|
||||
|
||||
|
||||
async def validate_access_token(access_code: str):
|
||||
async with httpx.AsyncClient() as client:
|
||||
url = (
|
||||
"https://backend.buildpicoapps.com/screenshot_to_code/validate_access_token"
|
||||
)
|
||||
data = json.dumps(
|
||||
{
|
||||
"access_code": access_code,
|
||||
"secret": os.environ.get("PICO_BACKEND_SECRET"),
|
||||
}
|
||||
)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
response = await client.post(url, content=data, headers=headers)
|
||||
response_data = response.json()
|
||||
return response_data
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -1,11 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# exit on error
|
||||
set -o errexit
|
||||
|
||||
echo "Installing the latest version of poetry..."
|
||||
pip install --upgrade pip
|
||||
pip install poetry==1.4.1
|
||||
|
||||
rm poetry.lock
|
||||
poetry lock
|
||||
python -m poetry install
|
||||
@ -1,13 +0,0 @@
|
||||
# Useful for debugging purposes when you don't want to waste GPT4-Vision credits
|
||||
# Setting to True will stream a mock response instead of calling the OpenAI API
|
||||
# TODO: Should only be set to true when value is 'True', not any abitrary truthy value
|
||||
import os
|
||||
|
||||
|
||||
SHOULD_MOCK_AI_RESPONSE = bool(os.environ.get("MOCK", False))
|
||||
MODEL = os.environ.get("MODEL", 'gpt-4-vision')
|
||||
IS_MODEL_GEMINI = MODEL == 'gemini'
|
||||
|
||||
# Set to True when running in production (on the hosted version)
|
||||
# Used as a feature flag to enable or disable certain features
|
||||
IS_PROD = os.environ.get("IS_PROD", False)
|
||||
@ -1,67 +0,0 @@
|
||||
# Load environment variables first
|
||||
from typing import Any, Coroutine
|
||||
from dotenv import load_dotenv
|
||||
|
||||
from eval_config import EVALS_DIR
|
||||
from eval_utils import image_to_data_url
|
||||
|
||||
load_dotenv()
|
||||
|
||||
import os
|
||||
from llm import stream_openai_response
|
||||
from prompts import assemble_prompt
|
||||
import asyncio
|
||||
|
||||
from utils import pprint_prompt
|
||||
|
||||
|
||||
async def generate_code_core(image_url: str, stack: str) -> str:
|
||||
prompt_messages = assemble_prompt(image_url, stack)
|
||||
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
||||
openai_base_url = None
|
||||
|
||||
pprint_prompt(prompt_messages)
|
||||
|
||||
async def process_chunk(content: str):
|
||||
pass
|
||||
|
||||
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
|
||||
|
||||
|
||||
async def main():
|
||||
INPUT_DIR = EVALS_DIR + "/inputs"
|
||||
OUTPUT_DIR = EVALS_DIR + "/outputs"
|
||||
|
||||
# Get all the files in the directory (only grab pngs)
|
||||
evals = [f for f in os.listdir(INPUT_DIR) if f.endswith(".png")]
|
||||
|
||||
tasks: list[Coroutine[Any, Any, str]] = []
|
||||
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, "svg")
|
||||
tasks.append(task)
|
||||
|
||||
results = await asyncio.gather(*tasks)
|
||||
|
||||
os.makedirs(OUTPUT_DIR, exist_ok=True)
|
||||
|
||||
for filename, content in zip(evals, results):
|
||||
# File name is derived from the original filename in evals
|
||||
output_filename = f"{os.path.splitext(filename)[0]}.html"
|
||||
output_filepath = os.path.join(OUTPUT_DIR, output_filename)
|
||||
with open(output_filepath, "w") as file:
|
||||
file.write(content)
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
@ -1 +0,0 @@
|
||||
EVALS_DIR = "./evals"
|
||||
@ -1,7 +0,0 @@
|
||||
import base64
|
||||
|
||||
|
||||
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/png;base64,{encoded_string}"
|
||||
@ -1,121 +0,0 @@
|
||||
import asyncio
|
||||
import re
|
||||
from typing import Dict, List, Union
|
||||
from openai import AsyncOpenAI
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
async def process_tasks(prompts: List[str], api_key: str, base_url: str):
|
||||
tasks = [generate_image(prompt, api_key, base_url) for prompt in prompts]
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
processed_results: List[Union[str, None]] = []
|
||||
for result in results:
|
||||
if isinstance(result, Exception):
|
||||
print(f"An exception occurred: {result}")
|
||||
processed_results.append(None)
|
||||
else:
|
||||
processed_results.append(result)
|
||||
|
||||
return processed_results
|
||||
|
||||
|
||||
async def generate_image(prompt: str, api_key: str, base_url: str):
|
||||
client = AsyncOpenAI(api_key=api_key, base_url=base_url)
|
||||
image_params: Dict[str, Union[str, int]] = {
|
||||
"model": "dall-e-3",
|
||||
"quality": "standard",
|
||||
"style": "natural",
|
||||
"n": 1,
|
||||
"size": "1024x1024",
|
||||
"prompt": prompt,
|
||||
}
|
||||
res = await client.images.generate(**image_params)
|
||||
await client.close()
|
||||
return res.data[0].url
|
||||
|
||||
|
||||
def extract_dimensions(url: str):
|
||||
# Regular expression to match numbers in the format '300x200'
|
||||
matches = re.findall(r"(\d+)x(\d+)", url)
|
||||
|
||||
if matches:
|
||||
width, height = matches[0] # Extract the first match
|
||||
width = int(width)
|
||||
height = int(height)
|
||||
return (width, height)
|
||||
else:
|
||||
return (100, 100)
|
||||
|
||||
|
||||
def create_alt_url_mapping(code: str) -> Dict[str, str]:
|
||||
soup = BeautifulSoup(code, "html.parser")
|
||||
images = soup.find_all("img")
|
||||
|
||||
mapping: Dict[str, str] = {}
|
||||
|
||||
for image in images:
|
||||
if not image["src"].startswith("https://placehold.co"):
|
||||
mapping[image["alt"]] = image["src"]
|
||||
|
||||
return mapping
|
||||
|
||||
|
||||
async def generate_images(
|
||||
code: str, api_key: str, base_url: Union[str, None], image_cache: Dict[str, str]
|
||||
):
|
||||
# Find all images
|
||||
soup = BeautifulSoup(code, "html.parser")
|
||||
images = soup.find_all("img")
|
||||
|
||||
# Extract alt texts as image prompts
|
||||
alts = []
|
||||
for img in images:
|
||||
# Only include URL if the image starts with https://placehold.co
|
||||
# and it's not already in the image_cache
|
||||
if (
|
||||
img["src"].startswith("https://placehold.co")
|
||||
and image_cache.get(img.get("alt")) is None
|
||||
):
|
||||
alts.append(img.get("alt", None))
|
||||
|
||||
# Exclude images with no alt text
|
||||
alts = [alt for alt in alts if alt is not None]
|
||||
|
||||
# Remove duplicates
|
||||
prompts = list(set(alts))
|
||||
|
||||
# Return early if there are no images to replace
|
||||
if len(prompts) == 0:
|
||||
return code
|
||||
|
||||
# Generate images
|
||||
results = await process_tasks(prompts, api_key, base_url)
|
||||
|
||||
# Create a dict mapping alt text to image URL
|
||||
mapped_image_urls = dict(zip(prompts, results))
|
||||
|
||||
# Merge with image_cache
|
||||
mapped_image_urls = {**mapped_image_urls, **image_cache}
|
||||
|
||||
# Replace old image URLs with the generated URLs
|
||||
for img in images:
|
||||
# Skip images that don't start with https://placehold.co (leave them alone)
|
||||
if not img["src"].startswith("https://placehold.co"):
|
||||
continue
|
||||
|
||||
new_url = mapped_image_urls[img.get("alt")]
|
||||
|
||||
if new_url:
|
||||
# Set width and height attributes
|
||||
width, height = extract_dimensions(img["src"])
|
||||
img["width"] = width
|
||||
img["height"] = height
|
||||
# Replace img['src'] with the mapped image URL
|
||||
img["src"] = new_url
|
||||
else:
|
||||
print("Image generation failed for alt text:" + img.get("alt"))
|
||||
|
||||
# Return the modified HTML
|
||||
# (need to prettify it because BeautifulSoup messes up the formatting)
|
||||
return soup.prettify()
|
||||
@ -1,92 +0,0 @@
|
||||
IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Tailwind developer.
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" 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: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IMPORTED_CODE_REACT_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert React/Tailwind developer
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" 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 these script to include React so that it can run on a standalone page:
|
||||
<script src="https://unpkg.com/react/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.js"></script>
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IMPORTED_CODE_BOOTSTRAP_SYSTEM_PROMPT = """
|
||||
You are an expert Bootstrap developer.
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" 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 Bootstrap: <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IMPORTED_CODE_IONIC_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Ionic/Tailwind developer.
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" 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 these script to include Ionic so that it can run on a standalone page:
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" />
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- ionicons for icons, add the following <script > tags near the end of the page, right before the closing </body> tag:
|
||||
<script type="module">
|
||||
import ionicons from 'https://cdn.jsdelivr.net/npm/ionicons/+esm'
|
||||
</script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/ionicons/dist/esm/ionicons.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/ionicons/dist/collection/components/icon/icon.min.css" rel="stylesheet">
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IMPORTED_CODE_SVG_SYSTEM_PROMPT = """
|
||||
You are an expert at building SVGs.
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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.
|
||||
- You can use Google Fonts
|
||||
|
||||
Return only the full code in <svg></svg> tags.
|
||||
Do not include markdown "```" or "```svg" at the start or end.
|
||||
"""
|
||||
@ -1,96 +0,0 @@
|
||||
import io
|
||||
from typing import Awaitable, Callable, List, Any
|
||||
from openai import AsyncOpenAI
|
||||
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionChunk
|
||||
import google.generativeai as genai
|
||||
from PIL import Image
|
||||
import base64
|
||||
import os
|
||||
|
||||
|
||||
MODEL_GPT_4_VISION = "gpt-4-vision-preview"
|
||||
MODEL_GEMINI_PRO_VISION = "models/gemini-pro-vision"
|
||||
|
||||
async def format_messages_for_gemnini(messages: List[Any]):
|
||||
system_promt = messages[0]['content']
|
||||
image_base64 = messages[1]['content'][0]['image_url']['url']
|
||||
additional_prompt = messages[1]['content'][1]['text']
|
||||
|
||||
base64_data = image_base64.split(",")[1]
|
||||
decoded_bytes = base64.b64decode(base64_data)
|
||||
|
||||
image_bytes = io.BytesIO(decoded_bytes)
|
||||
|
||||
image = Image.open(image_bytes)
|
||||
|
||||
return [system_promt, image, additional_prompt]
|
||||
|
||||
async def stream_openai_response(
|
||||
messages: List[ChatCompletionMessageParam],
|
||||
api_key: str,
|
||||
base_url: str | None,
|
||||
callback: Callable[[str], Awaitable[None]],
|
||||
) -> str:
|
||||
client = AsyncOpenAI(api_key=api_key, base_url=base_url)
|
||||
|
||||
model = MODEL_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:
|
||||
params["max_tokens"] = 4096
|
||||
params["temperature"] = 0
|
||||
|
||||
stream = await client.chat.completions.create(**params) # type: ignore
|
||||
full_response = ""
|
||||
async for chunk in stream: # type: ignore
|
||||
assert isinstance(chunk, ChatCompletionChunk)
|
||||
content = chunk.choices[0].delta.content or ""
|
||||
full_response += content
|
||||
await callback(content)
|
||||
|
||||
await client.close()
|
||||
|
||||
return full_response
|
||||
|
||||
async def stream_gemini_response(
|
||||
messages: List[Any] = [],
|
||||
api_key: str = os.getenv('GOOGLE_API_KEY', ''),
|
||||
callback: Callable[[str], Awaitable[None]] | None = None,
|
||||
) -> str:
|
||||
genai.configure(api_key=api_key)
|
||||
|
||||
model = genai.GenerativeModel('gemini-pro-vision')
|
||||
general_config = {
|
||||
"max_output_tokens": 2048,
|
||||
"temperature": 0.4,
|
||||
"top_p": 1,
|
||||
"top_k": 32
|
||||
}
|
||||
|
||||
formatted_messages = await format_messages_for_gemnini(messages)
|
||||
response = model.generate_content(
|
||||
formatted_messages,
|
||||
stream=True,
|
||||
generation_config=general_config # type: ignore
|
||||
)
|
||||
response.resolve()
|
||||
result = response.text
|
||||
|
||||
while "</html>" not in result:
|
||||
continue_prompt = "Generate the rest of the code below"
|
||||
formatted_messages.append(continue_prompt)
|
||||
formatted_messages.append(result)
|
||||
|
||||
response = model.generate_content(
|
||||
formatted_messages,
|
||||
stream=True,
|
||||
generation_config=general_config # type: ignore
|
||||
)
|
||||
response.resolve()
|
||||
|
||||
result += response.text
|
||||
|
||||
return result
|
||||
@ -1,34 +0,0 @@
|
||||
# Load environment variables first
|
||||
from dotenv import load_dotenv
|
||||
from pathlib import Path
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from routes import screenshot, generate_code, home, evals
|
||||
|
||||
app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
|
||||
|
||||
# Configure CORS settings
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Mount assets folder
|
||||
assets = Path(__file__).resolve().parent / "assets"
|
||||
if not assets.exists():
|
||||
assets.mkdir()
|
||||
app.mount('/assets', StaticFiles(directory=assets, html=True), name='static')
|
||||
|
||||
# Add routes
|
||||
app.include_router(generate_code.router)
|
||||
app.include_router(screenshot.router)
|
||||
app.include_router(home.router)
|
||||
app.include_router(evals.router)
|
||||
@ -1,208 +0,0 @@
|
||||
import asyncio
|
||||
from typing import Awaitable, Callable
|
||||
|
||||
|
||||
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])
|
||||
await asyncio.sleep(0.01)
|
||||
|
||||
return code_to_return
|
||||
|
||||
|
||||
APPLE_MOCK_CODE = """<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Product Showcase</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-black text-white">
|
||||
<nav class="py-6">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex justify-between items-center">
|
||||
<div class="flex items-center">
|
||||
<img src="https://placehold.co/24x24" alt="Company Logo" class="mr-8">
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">Store</a>
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">Mac</a>
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">iPad</a>
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">iPhone</a>
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">Watch</a>
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">Vision</a>
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">AirPods</a>
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">TV & Home</a>
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">Entertainment</a>
|
||||
<a href="#" class="text-white text-sm font-medium mr-4">Accessories</a>
|
||||
<a href="#" class="text-white text-sm font-medium">Support</a>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<a href="#" class="text-white text-sm font-medium mr-4"><i class="fas fa-search"></i></a>
|
||||
<a href="#" class="text-white text-sm font-medium"><i class="fas fa-shopping-bag"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="mt-8">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<img src="https://placehold.co/100x100" alt="Brand Logo" class="mx-auto mb-4">
|
||||
<h1 class="text-5xl font-bold mb-4">WATCH SERIES 9</h1>
|
||||
<p class="text-2xl font-medium mb-8">Smarter. Brighter. Mightier.</p>
|
||||
<div class="flex justify-center space-x-4">
|
||||
<a href="#" class="text-blue-600 text-sm font-medium">Learn more ></a>
|
||||
<a href="#" class="text-blue-600 text-sm font-medium">Buy ></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex justify-center mt-12">
|
||||
<img src="https://placehold.co/500x300" alt="Product image of a smartwatch with a pink band and a circular interface displaying various health metrics." class="mr-8">
|
||||
<img src="https://placehold.co/500x300" alt="Product image of a smartwatch with a blue band and a square interface showing a classic analog clock face." class="ml-8">
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</body>
|
||||
</html>"""
|
||||
|
||||
NYTIMES_MOCK_CODE = """
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>The New York Times - News</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;400;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Libre Franklin', sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<div class="container mx-auto px-4">
|
||||
<header class="border-b border-gray-300 py-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center space-x-4">
|
||||
<button class="text-gray-700"><i class="fas fa-bars"></i></button>
|
||||
<button class="text-gray-700"><i class="fas fa-search"></i></button>
|
||||
<div class="text-xs uppercase tracking-widest">Tuesday, November 14, 2023<br>Today's Paper</div>
|
||||
</div>
|
||||
<div>
|
||||
<img src="https://placehold.co/200x50?text=The+New+York+Times+Logo" alt="The New York Times Logo" class="h-8">
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button class="bg-black text-white px-4 py-1 text-xs uppercase tracking-widest">Give the times</button>
|
||||
<div class="text-xs">Account</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="flex justify-between items-center py-4">
|
||||
<div class="flex space-x-4">
|
||||
<a href="#" class="text-xs uppercase tracking-widest text-gray-700">U.S.</a>
|
||||
<!-- Add other navigation links as needed -->
|
||||
</div>
|
||||
<div class="flex space-x-4">
|
||||
<a href="#" class="text-xs uppercase tracking-widest text-gray-700">Cooking</a>
|
||||
<!-- Add other navigation links as needed -->
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<section class="py-6">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="col-span-2">
|
||||
<article class="mb-4">
|
||||
<h2 class="text-xl font-bold mb-2">Israeli Military Raids Gaza’s Largest Hospital</h2>
|
||||
<p class="text-gray-700 mb-2">Israeli troops have entered the Al-Shifa Hospital complex, where conditions have grown dire and Israel says Hamas fighters are embedded.</p>
|
||||
<a href="#" class="text-blue-600 text-sm">See more updates <i class="fas fa-external-link-alt"></i></a>
|
||||
</article>
|
||||
<!-- Repeat for each news item -->
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<article class="mb-4">
|
||||
<img src="https://placehold.co/300x200?text=News+Image" alt="Flares and plumes of smoke over the northern Gaza skyline on Tuesday." class="mb-2">
|
||||
<h2 class="text-xl font-bold mb-2">From Elvis to Elopements, the Evolution of the Las Vegas Wedding</h2>
|
||||
<p class="text-gray-700 mb-2">The glittering city that attracts thousands of couples seeking unconventional nuptials has grown beyond the drive-through wedding.</p>
|
||||
<a href="#" class="text-blue-600 text-sm">8 MIN READ</a>
|
||||
</article>
|
||||
<!-- Repeat for each news item -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
NO_IMAGES_NYTIMES_MOCK_CODE = """
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>The New York Times - News</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Libre+Franklin:wght@300;400;700&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Libre Franklin', sans-serif;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body class="bg-gray-100">
|
||||
<div class="container mx-auto px-4">
|
||||
<header class="border-b border-gray-300 py-4">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center space-x-4">
|
||||
<button class="text-gray-700"><i class="fas fa-bars"></i></button>
|
||||
<button class="text-gray-700"><i class="fas fa-search"></i></button>
|
||||
<div class="text-xs uppercase tracking-widest">Tuesday, November 14, 2023<br>Today's Paper</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-4">
|
||||
<button class="bg-black text-white px-4 py-1 text-xs uppercase tracking-widest">Give the times</button>
|
||||
<div class="text-xs">Account</div>
|
||||
</div>
|
||||
</div>
|
||||
<nav class="flex justify-between items-center py-4">
|
||||
<div class="flex space-x-4">
|
||||
<a href="#" class="text-xs uppercase tracking-widest text-gray-700">U.S.</a>
|
||||
<!-- Add other navigation links as needed -->
|
||||
</div>
|
||||
<div class="flex space-x-4">
|
||||
<a href="#" class="text-xs uppercase tracking-widest text-gray-700">Cooking</a>
|
||||
<!-- Add other navigation links as needed -->
|
||||
</div>
|
||||
</nav>
|
||||
</header>
|
||||
<main>
|
||||
<section class="py-6">
|
||||
<div class="grid grid-cols-3 gap-4">
|
||||
<div class="col-span-2">
|
||||
<article class="mb-4">
|
||||
<h2 class="text-xl font-bold mb-2">Israeli Military Raids Gaza’s Largest Hospital</h2>
|
||||
<p class="text-gray-700 mb-2">Israeli troops have entered the Al-Shifa Hospital complex, where conditions have grown dire and Israel says Hamas fighters are embedded.</p>
|
||||
<a href="#" class="text-blue-600 text-sm">See more updates <i class="fas fa-external-link-alt"></i></a>
|
||||
</article>
|
||||
<!-- Repeat for each news item -->
|
||||
</div>
|
||||
<div class="col-span-1">
|
||||
<article class="mb-4">
|
||||
<h2 class="text-xl font-bold mb-2">From Elvis to Elopements, the Evolution of the Las Vegas Wedding</h2>
|
||||
<p class="text-gray-700 mb-2">The glittering city that attracts thousands of couples seeking unconventional nuptials has grown beyond the drive-through wedding.</p>
|
||||
<a href="#" class="text-blue-600 text-sm">8 MIN READ</a>
|
||||
</article>
|
||||
<!-- Repeat for each news item -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
1001
backend/poetry.lock
generated
1001
backend/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,116 +0,0 @@
|
||||
from typing import List, Union
|
||||
|
||||
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionContentPartParam
|
||||
|
||||
from imported_code_prompts import (
|
||||
IMPORTED_CODE_BOOTSTRAP_SYSTEM_PROMPT,
|
||||
IMPORTED_CODE_IONIC_TAILWIND_SYSTEM_PROMPT,
|
||||
IMPORTED_CODE_REACT_TAILWIND_SYSTEM_PROMPT,
|
||||
IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT,
|
||||
IMPORTED_CODE_SVG_SYSTEM_PROMPT,
|
||||
)
|
||||
from screenshot_system_prompts import (
|
||||
BOOTSTRAP_SYSTEM_PROMPT,
|
||||
IONIC_TAILWIND_SYSTEM_PROMPT,
|
||||
REACT_TAILWIND_SYSTEM_PROMPT,
|
||||
TAILWIND_SYSTEM_PROMPT,
|
||||
SVG_SYSTEM_PROMPT,
|
||||
)
|
||||
|
||||
|
||||
USER_PROMPT = """
|
||||
Generate code for a web page that looks exactly like this.
|
||||
"""
|
||||
|
||||
SVG_USER_PROMPT = """
|
||||
Generate code for a SVG that looks exactly like this.
|
||||
"""
|
||||
|
||||
|
||||
def assemble_imported_code_prompt(
|
||||
code: str, stack: str, result_image_data_url: Union[str, None] = None
|
||||
) -> List[ChatCompletionMessageParam]:
|
||||
system_content = IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT
|
||||
if stack == "html_tailwind":
|
||||
system_content = IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT
|
||||
elif stack == "react_tailwind":
|
||||
system_content = IMPORTED_CODE_REACT_TAILWIND_SYSTEM_PROMPT
|
||||
elif stack == "bootstrap":
|
||||
system_content = IMPORTED_CODE_BOOTSTRAP_SYSTEM_PROMPT
|
||||
elif stack == "ionic_tailwind":
|
||||
system_content = IMPORTED_CODE_IONIC_TAILWIND_SYSTEM_PROMPT
|
||||
elif stack == "svg":
|
||||
system_content = IMPORTED_CODE_SVG_SYSTEM_PROMPT
|
||||
else:
|
||||
raise Exception("Code config is not one of available options")
|
||||
|
||||
user_content = (
|
||||
"Here is the code of the app: " + code
|
||||
if stack != "svg"
|
||||
else "Here is the code of the SVG: " + code
|
||||
)
|
||||
return [
|
||||
{
|
||||
"role": "system",
|
||||
"content": system_content,
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": user_content,
|
||||
},
|
||||
]
|
||||
# TODO: Use result_image_data_url
|
||||
|
||||
|
||||
def assemble_prompt(
|
||||
image_data_url: str,
|
||||
generated_code_config: str,
|
||||
result_image_data_url: Union[str, None] = None,
|
||||
) -> List[ChatCompletionMessageParam]:
|
||||
# Set the system prompt based on the output settings
|
||||
system_content = TAILWIND_SYSTEM_PROMPT
|
||||
if generated_code_config == "html_tailwind":
|
||||
system_content = TAILWIND_SYSTEM_PROMPT
|
||||
elif generated_code_config == "react_tailwind":
|
||||
system_content = REACT_TAILWIND_SYSTEM_PROMPT
|
||||
elif generated_code_config == "bootstrap":
|
||||
system_content = BOOTSTRAP_SYSTEM_PROMPT
|
||||
elif generated_code_config == "ionic_tailwind":
|
||||
system_content = IONIC_TAILWIND_SYSTEM_PROMPT
|
||||
elif generated_code_config == "svg":
|
||||
system_content = SVG_SYSTEM_PROMPT
|
||||
else:
|
||||
raise Exception("Code config is not one of available options")
|
||||
|
||||
user_prompt = USER_PROMPT if generated_code_config != "svg" else SVG_USER_PROMPT
|
||||
|
||||
user_content: List[ChatCompletionContentPartParam] = [
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {"url": image_data_url, "detail": "high"},
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": user_prompt,
|
||||
},
|
||||
]
|
||||
|
||||
# Include the result image if it exists
|
||||
if result_image_data_url:
|
||||
user_content.insert(
|
||||
1,
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {"url": result_image_data_url, "detail": "high"},
|
||||
},
|
||||
)
|
||||
return [
|
||||
{
|
||||
"role": "system",
|
||||
"content": system_content,
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": user_content,
|
||||
},
|
||||
]
|
||||
@ -1,26 +0,0 @@
|
||||
[tool.poetry]
|
||||
name = "backend"
|
||||
version = "0.1.0"
|
||||
description = ""
|
||||
authors = ["Abi Raja <abimanyuraja@gmail.com>"]
|
||||
license = "MIT"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
fastapi = "^0.95.0"
|
||||
uvicorn = "^0.24.0.post1"
|
||||
websockets = "^12.0"
|
||||
openai = "^1.2.4"
|
||||
python-dotenv = "^1.0.0"
|
||||
beautifulsoup4 = "^4.12.2"
|
||||
httpx = "^0.25.1"
|
||||
google-generativeai = "0.3.0"
|
||||
google-ai-generativelanguage = "0.4.0"
|
||||
pillow = "^10.1.0"
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
pytest = "^7.4.3"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
@ -1,46 +0,0 @@
|
||||
import os
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
from eval_utils import image_to_data_url
|
||||
from eval_config import EVALS_DIR
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class Eval(BaseModel):
|
||||
input: str
|
||||
output: str
|
||||
|
||||
|
||||
@router.get("/evals")
|
||||
async def get_evals():
|
||||
# Get all evals from EVALS_DIR
|
||||
input_dir = EVALS_DIR + "/inputs"
|
||||
output_dir = EVALS_DIR + "/outputs"
|
||||
|
||||
evals: list[Eval] = []
|
||||
for file in os.listdir(input_dir):
|
||||
if file.endswith(".png"):
|
||||
input_file_path = os.path.join(input_dir, file)
|
||||
input_file = await image_to_data_url(input_file_path)
|
||||
|
||||
# Construct the corresponding output file name
|
||||
output_file_name = file.replace(".png", ".html")
|
||||
output_file_path = os.path.join(output_dir, output_file_name)
|
||||
|
||||
# Check if the output file exists
|
||||
if os.path.exists(output_file_path):
|
||||
with open(output_file_path, "r") as f:
|
||||
output_file_data = f.read()
|
||||
else:
|
||||
output_file_data = "Output file not found."
|
||||
|
||||
evals.append(
|
||||
Eval(
|
||||
input=input_file,
|
||||
output=output_file_data,
|
||||
)
|
||||
)
|
||||
|
||||
return evals
|
||||
@ -1,290 +0,0 @@
|
||||
import os
|
||||
import traceback
|
||||
from fastapi import APIRouter, WebSocket
|
||||
import openai
|
||||
from config import IS_PROD, SHOULD_MOCK_AI_RESPONSE, IS_MODEL_GEMINI
|
||||
from llm import stream_gemini_response, stream_openai_response
|
||||
from openai.types.chat import ChatCompletionMessageParam
|
||||
from mock_llm import mock_completion
|
||||
from typing import Dict, List
|
||||
from image_generation import create_alt_url_mapping, generate_images
|
||||
from prompts import assemble_imported_code_prompt, assemble_prompt
|
||||
from access_token import validate_access_token
|
||||
from datetime import datetime
|
||||
import json
|
||||
|
||||
from utils import pprint_prompt # type: ignore
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def write_logs(prompt_messages: List[ChatCompletionMessageParam], completion: str):
|
||||
# Get the logs path from environment, default to the current working directory
|
||||
logs_path = os.environ.get("LOGS_PATH", os.getcwd())
|
||||
|
||||
# Create run_logs directory if it doesn't exist within the specified logs path
|
||||
logs_directory = os.path.join(logs_path, "run_logs")
|
||||
if not os.path.exists(logs_directory):
|
||||
os.makedirs(logs_directory)
|
||||
|
||||
print("Writing to logs directory:", logs_directory)
|
||||
|
||||
# Generate a unique filename using the current timestamp within the logs directory
|
||||
filename = datetime.now().strftime(f"{logs_directory}/messages_%Y%m%d_%H%M%S.json")
|
||||
|
||||
# Write the messages dict into a new file for each run
|
||||
with open(filename, "w") as f:
|
||||
f.write(json.dumps({"prompt": prompt_messages, "completion": completion}))
|
||||
|
||||
|
||||
@router.websocket("/generate-code")
|
||||
async def stream_code(websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
|
||||
print("Incoming websocket connection...")
|
||||
|
||||
async def throw_error(
|
||||
message: str,
|
||||
):
|
||||
await websocket.send_json({"type": "error", "value": message})
|
||||
await websocket.close()
|
||||
|
||||
# TODO: Are the values always strings?
|
||||
params: Dict[str, str] = await websocket.receive_json()
|
||||
|
||||
print("Received params")
|
||||
|
||||
# Read the code config settings from the request. Fall back to default if not provided.
|
||||
generated_code_config = ""
|
||||
if "generatedCodeConfig" in params and params["generatedCodeConfig"]:
|
||||
generated_code_config = params["generatedCodeConfig"]
|
||||
print(f"Generating {generated_code_config} code")
|
||||
|
||||
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
|
||||
# If neither is provided, we throw an error.
|
||||
openai_api_key = os.getenv('OPENAI_API_KEY')
|
||||
openai_base_url = None
|
||||
google_api_key = os.getenv('GOOGLE_API_KEY')
|
||||
model = 'model/gemini-pro-vision' if IS_MODEL_GEMINI else 'gpt-4-vision'
|
||||
should_generate_images = False
|
||||
if "accessCode" in params and params["accessCode"]:
|
||||
print("Access code - using platform API key")
|
||||
res = await validate_access_token(params["accessCode"])
|
||||
if res["success"]:
|
||||
openai_api_key = os.environ.get("PLATFORM_OPENAI_API_KEY")
|
||||
google_api_key = os.environ.get("PLATFORM_GOOGLE_API_KEY")
|
||||
else:
|
||||
await websocket.send_json(
|
||||
{
|
||||
"type": "error",
|
||||
"value": res["failure_reason"],
|
||||
}
|
||||
)
|
||||
return
|
||||
else:
|
||||
if params['model'] == 'models/gemini-pro-vision':
|
||||
print('Using the Gemini Pro Vision Model')
|
||||
model = params['model']
|
||||
google_api_key = params['googleApiKey']
|
||||
else:
|
||||
print('Using the GPT 4 Vision Model')
|
||||
if params["openAiApiKey"]:
|
||||
openai_api_key = params["openAiApiKey"]
|
||||
print("Using OpenAI API key from client-side settings dialog")
|
||||
else:
|
||||
if openai_api_key:
|
||||
print("Using OpenAI API key from environment variable")
|
||||
|
||||
if (model == 'gpt-4-vision' and not openai_api_key) or (model == 'models/gemini-pro-vision' and not google_api_key):
|
||||
print("API key not found")
|
||||
await websocket.send_json(
|
||||
{
|
||||
"type": "error",
|
||||
"value": "No API key found. Please add your API key in the settings dialog or add it to backend/.env file.",
|
||||
}
|
||||
)
|
||||
return
|
||||
|
||||
if model == 'gpt-4-vision':
|
||||
# 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
|
||||
if not os.environ.get("IS_PROD"):
|
||||
if "openAiBaseURL" in params and params["openAiBaseURL"]:
|
||||
openai_base_url = params["openAiBaseURL"]
|
||||
print("Using OpenAI Base URL from client-side settings dialog")
|
||||
else:
|
||||
openai_base_url = os.environ.get("OPENAI_BASE_URL")
|
||||
if openai_base_url:
|
||||
print("Using OpenAI Base URL from environment variable")
|
||||
|
||||
if not openai_base_url:
|
||||
print("Using official OpenAI URL")
|
||||
|
||||
# Get the image generation flag from the request. Fall back to True if not provided.
|
||||
should_generate_images = (
|
||||
params["isImageGenerationEnabled"]
|
||||
if "isImageGenerationEnabled" in params
|
||||
else True
|
||||
)
|
||||
|
||||
print("generating code...")
|
||||
await websocket.send_json({"type": "status", "value": "Generating code..."})
|
||||
|
||||
async def process_chunk(content: str):
|
||||
await websocket.send_json({"type": "chunk", "value": content})
|
||||
|
||||
# Image cache for updates so that we don't have to regenerate images
|
||||
image_cache: Dict[str, str] = {}
|
||||
|
||||
# If this generation started off with imported code, we need to assemble the prompt differently
|
||||
if params.get("isImportedFromCode") and params["isImportedFromCode"]:
|
||||
original_imported_code = params["history"][0]
|
||||
prompt_messages = assemble_imported_code_prompt(
|
||||
original_imported_code, generated_code_config
|
||||
)
|
||||
for index, text in enumerate(params["history"][1:]):
|
||||
if index % 2 == 0:
|
||||
message: ChatCompletionMessageParam = {
|
||||
"role": "user",
|
||||
"content": text,
|
||||
}
|
||||
else:
|
||||
message: ChatCompletionMessageParam = {
|
||||
"role": "assistant",
|
||||
"content": text,
|
||||
}
|
||||
prompt_messages.append(message)
|
||||
else:
|
||||
# Assemble the prompt
|
||||
try:
|
||||
if params.get("resultImage") and params["resultImage"]:
|
||||
prompt_messages = assemble_prompt(
|
||||
params["image"], generated_code_config, params["resultImage"]
|
||||
)
|
||||
else:
|
||||
prompt_messages = assemble_prompt(
|
||||
params["image"], generated_code_config
|
||||
)
|
||||
except:
|
||||
await websocket.send_json(
|
||||
{
|
||||
"type": "error",
|
||||
"value": "Error assembling prompt. Contact support at support@picoapps.xyz",
|
||||
}
|
||||
)
|
||||
await websocket.close()
|
||||
return
|
||||
|
||||
if params["generationType"] == "update":
|
||||
# Transform the history tree into message format
|
||||
# TODO: Move this to frontend
|
||||
for index, text in enumerate(params["history"]):
|
||||
if index % 2 == 0:
|
||||
message: ChatCompletionMessageParam = {
|
||||
"role": "assistant",
|
||||
"content": text,
|
||||
}
|
||||
else:
|
||||
message: ChatCompletionMessageParam = {
|
||||
"role": "user",
|
||||
"content": text,
|
||||
}
|
||||
prompt_messages.append(message)
|
||||
|
||||
image_cache = create_alt_url_mapping(params["history"][-2])
|
||||
|
||||
# pprint_prompt(prompt_messages)
|
||||
|
||||
if SHOULD_MOCK_AI_RESPONSE:
|
||||
completion = await mock_completion(process_chunk)
|
||||
elif IS_MODEL_GEMINI:
|
||||
completion = await stream_gemini_response(
|
||||
prompt_messages,
|
||||
api_key=google_api_key,
|
||||
callback=lambda x: process_chunk(x),
|
||||
)
|
||||
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),
|
||||
)
|
||||
except openai.AuthenticationError as e:
|
||||
print("[GENERATE_CODE] Authentication failed", e)
|
||||
error_message = (
|
||||
"Incorrect OpenAI key. Please make sure your OpenAI API key is correct, or create a new OpenAI API key on your OpenAI dashboard."
|
||||
+ (
|
||||
" Alternatively, you can purchase code generation credits directly on this website."
|
||||
if IS_PROD
|
||||
else ""
|
||||
)
|
||||
)
|
||||
return await throw_error(error_message)
|
||||
except openai.NotFoundError as e:
|
||||
print("[GENERATE_CODE] Model not found", e)
|
||||
error_message = (
|
||||
e.message
|
||||
+ ". Please make sure you have followed the instructions correctly to obtain an OpenAI key with GPT vision access: https://github.com/abi/screenshot-to-code/blob/main/Troubleshooting.md"
|
||||
+ (
|
||||
" Alternatively, you can purchase code generation credits directly on this website."
|
||||
if IS_PROD
|
||||
else ""
|
||||
)
|
||||
)
|
||||
return await throw_error(error_message)
|
||||
except openai.RateLimitError as e:
|
||||
print("[GENERATE_CODE] Rate limit exceeded", e)
|
||||
error_message = (
|
||||
"OpenAI error - 'You exceeded your current quota, please check your plan and billing details.'"
|
||||
+ (
|
||||
" Alternatively, you can purchase code generation credits directly on this website."
|
||||
if IS_PROD
|
||||
else ""
|
||||
)
|
||||
)
|
||||
return await throw_error(error_message)
|
||||
|
||||
# Write the messages dict into a log so that we can debug later
|
||||
write_logs(prompt_messages, completion)
|
||||
|
||||
try:
|
||||
if should_generate_images:
|
||||
await websocket.send_json(
|
||||
{"type": "status", "value": "Generating images..."}
|
||||
)
|
||||
updated_html = await generate_images(
|
||||
completion,
|
||||
api_key=openai_api_key,
|
||||
base_url=openai_base_url,
|
||||
image_cache=image_cache,
|
||||
)
|
||||
else:
|
||||
updated_html = completion
|
||||
await websocket.send_json({"type": "setCode", "value": updated_html})
|
||||
await websocket.send_json(
|
||||
{"type": "status", "value": "Code generation complete."}
|
||||
)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
print("Image generation failed", e)
|
||||
# Send set code even if image generation fails since that triggers
|
||||
# the frontend to update history
|
||||
await websocket.send_json({"type": "setCode", "value": completion})
|
||||
await websocket.send_json(
|
||||
{"type": "status", "value": "Image generation failed but code is complete."}
|
||||
)
|
||||
except RuntimeError as e:
|
||||
traceback.print_exc()
|
||||
print("Code generation failed", e)
|
||||
# Send set code even if image generation fails since that triggers
|
||||
# the frontend to update history
|
||||
await websocket.send_json({"type": "setCode", "value": completion})
|
||||
await websocket.send_json(
|
||||
{"type": "status", "value": "Code generation failed but code is complete."}
|
||||
)
|
||||
|
||||
await websocket.close()
|
||||
@ -1,17 +0,0 @@
|
||||
from fastapi import APIRouter
|
||||
from fastapi.responses import HTMLResponse
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def get_status():
|
||||
html_path = Path('templates', 'index.html').resolve()
|
||||
html_content = ''
|
||||
with html_path.open('rt') as file:
|
||||
html_content = file.read()
|
||||
return HTMLResponse(
|
||||
content=html_content
|
||||
)
|
||||
@ -1,66 +0,0 @@
|
||||
import base64
|
||||
from fastapi import APIRouter
|
||||
from pydantic import BaseModel
|
||||
import httpx
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
def bytes_to_data_url(image_bytes: bytes, mime_type: str) -> str:
|
||||
base64_image = base64.b64encode(image_bytes).decode("utf-8")
|
||||
return f"data:{mime_type};base64,{base64_image}"
|
||||
|
||||
|
||||
async def capture_screenshot(
|
||||
target_url: str, api_key: str, device: str = "desktop"
|
||||
) -> bytes:
|
||||
api_base_url = "https://api.screenshotone.com/take"
|
||||
|
||||
params = {
|
||||
"access_key": api_key,
|
||||
"url": target_url,
|
||||
"full_page": "true",
|
||||
"device_scale_factor": "1",
|
||||
"format": "png",
|
||||
"block_ads": "true",
|
||||
"block_cookie_banners": "true",
|
||||
"block_trackers": "true",
|
||||
"cache": "false",
|
||||
"viewport_width": "342",
|
||||
"viewport_height": "684",
|
||||
}
|
||||
|
||||
if device == "desktop":
|
||||
params["viewport_width"] = "1280"
|
||||
params["viewport_height"] = "832"
|
||||
|
||||
async with httpx.AsyncClient(timeout=60) as client:
|
||||
response = await client.get(api_base_url, params=params)
|
||||
if response.status_code == 200 and response.content:
|
||||
return response.content
|
||||
else:
|
||||
raise Exception("Error taking screenshot")
|
||||
|
||||
|
||||
class ScreenshotRequest(BaseModel):
|
||||
url: str
|
||||
apiKey: str
|
||||
|
||||
|
||||
class ScreenshotResponse(BaseModel):
|
||||
url: str
|
||||
|
||||
|
||||
@router.post("/api/screenshot")
|
||||
async def app_screenshot(request: ScreenshotRequest):
|
||||
# Extract the URL from the request body
|
||||
url = request.url
|
||||
api_key = request.apiKey
|
||||
|
||||
# TODO: Add error handling
|
||||
image_bytes = await capture_screenshot(url, api_key=api_key)
|
||||
|
||||
# Convert the image bytes to a data url
|
||||
data_url = bytes_to_data_url(image_bytes, "image/png")
|
||||
|
||||
return ScreenshotResponse(url=data_url)
|
||||
@ -1,130 +0,0 @@
|
||||
TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Tailwind developer
|
||||
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.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
BOOTSTRAP_SYSTEM_PROMPT = """
|
||||
You are an expert Bootstrap developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using Bootstrap, 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.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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 Bootstrap: <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
REACT_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert React/Tailwind developer
|
||||
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.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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 these script to include React so that it can run on a standalone page:
|
||||
<script src="https://unpkg.com/react/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.js"></script>
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IONIC_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Ionic/Tailwind developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using Ionic 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.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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 these script to include Ionic so that it can run on a standalone page:
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" />
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- ionicons for icons, add the following <script > tags near the end of the page, right before the closing </body> tag:
|
||||
<script type="module">
|
||||
import ionicons from 'https://cdn.jsdelivr.net/npm/ionicons/+esm'
|
||||
</script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/ionicons/dist/esm/ionicons.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/ionicons/dist/collection/components/icon/icon.min.css" rel="stylesheet">
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
|
||||
SVG_SYSTEM_PROMPT = """
|
||||
You are an expert at building SVGs.
|
||||
You take screenshots of a reference web page from the user, and then build a SVG that looks exactly like the screenshot.
|
||||
|
||||
- Make sure the SVG 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.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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.
|
||||
- You can use Google Fonts
|
||||
|
||||
Return only the full code in <svg></svg> tags.
|
||||
Do not include markdown "```" or "```svg" at the start or end.
|
||||
"""
|
||||
@ -1 +0,0 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="UTF-8"/><link rel="icon" type="image/svg+xml" href="https://picoapps.xyz/favicon.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><link rel="preconnect" href="https://fonts.googleapis.com"/><link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/><link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;700&display=swap" rel="stylesheet"/><title>Screenshot to Code</title><meta property="og:title" content="Screenshot to Code"/><meta property="og:description" content="Convert any screenshot or design to clean code"/><meta property="og:image" content="https://screenshottocode.com/brand/twitter-summary-card.png"/><meta property="og:image:width" content="1200"/><meta property="og:image:height" content="628"/><meta property="og:url" content="https://screenshottocode.com"/><meta property="og:type" content="website"/><meta name="twitter:card" content="summary_large_image"/><meta name="twitter:site" content="@picoapps"/><meta name="twitter:title" content="Screenshot to Code"/><meta name="twitter:description" content="Convert any screenshot or design to clean code"/><meta name="twitter:image" content="https://screenshottocode.com/brand/twitter-summary-card.png"/><script type="module" crossorigin src="./assets/index-f02835e2.js"></script><link rel="stylesheet" href="./assets/index-a937a359.css"></head><body><div id="root"></div></body></html>
|
||||
@ -1,307 +0,0 @@
|
||||
from prompts import assemble_imported_code_prompt, assemble_prompt
|
||||
|
||||
TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Tailwind developer
|
||||
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.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
BOOTSTRAP_SYSTEM_PROMPT = """
|
||||
You are an expert Bootstrap developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using Bootstrap, 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.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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 Bootstrap: <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
REACT_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert React/Tailwind developer
|
||||
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.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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 these script to include React so that it can run on a standalone page:
|
||||
<script src="https://unpkg.com/react/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.js"></script>
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IONIC_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Ionic/Tailwind developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using Ionic 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.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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 these script to include Ionic so that it can run on a standalone page:
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" />
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- ionicons for icons, add the following <script > tags near the end of the page, right before the closing </body> tag:
|
||||
<script type="module">
|
||||
import ionicons from 'https://cdn.jsdelivr.net/npm/ionicons/+esm'
|
||||
</script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/ionicons/dist/esm/ionicons.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/ionicons/dist/collection/components/icon/icon.min.css" rel="stylesheet">
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
SVG_SYSTEM_PROMPT = """
|
||||
You are an expert at building SVGs.
|
||||
You take screenshots of a reference web page from the user, and then build a SVG that looks exactly like the screenshot.
|
||||
|
||||
- Make sure the SVG 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.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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.
|
||||
- You can use Google Fonts
|
||||
|
||||
Return only the full code in <svg></svg> tags.
|
||||
Do not include markdown "```" or "```svg" at the start or end.
|
||||
"""
|
||||
|
||||
IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Tailwind developer.
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" 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: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IMPORTED_CODE_REACT_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert React/Tailwind developer
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" 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 these script to include React so that it can run on a standalone page:
|
||||
<script src="https://unpkg.com/react/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.js"></script>
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IMPORTED_CODE_BOOTSTRAP_SYSTEM_PROMPT = """
|
||||
You are an expert Bootstrap developer.
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" 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 Bootstrap: <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IMPORTED_CODE_IONIC_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Ionic/Tailwind developer.
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" 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 these script to include Ionic so that it can run on a standalone page:
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" />
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- ionicons for icons, add the following <script > tags near the end of the page, right before the closing </body> tag:
|
||||
<script type="module">
|
||||
import ionicons from 'https://cdn.jsdelivr.net/npm/ionicons/+esm'
|
||||
</script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/ionicons/dist/esm/ionicons.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/ionicons/dist/collection/components/icon/icon.min.css" rel="stylesheet">
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
IMPORTED_CODE_SVG_SYSTEM_PROMPT = """
|
||||
You are an expert at building SVGs.
|
||||
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- 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 "<!-- Repeat for each news item -->" 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.
|
||||
- You can use Google Fonts
|
||||
|
||||
Return only the full code in <svg></svg> tags.
|
||||
Do not include markdown "```" or "```svg" at the start or end.
|
||||
"""
|
||||
|
||||
USER_PROMPT = """
|
||||
Generate code for a web page that looks exactly like this.
|
||||
"""
|
||||
|
||||
SVG_USER_PROMPT = """
|
||||
Generate code for a SVG that looks exactly like this.
|
||||
"""
|
||||
|
||||
|
||||
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[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[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[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[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[1]["content"][2]["text"] == SVG_USER_PROMPT # type: ignore
|
||||
|
||||
|
||||
def test_imported_code_prompts():
|
||||
tailwind_prompt = assemble_imported_code_prompt(
|
||||
"code", "html_tailwind", "result_image_data_url"
|
||||
)
|
||||
expected_tailwind_prompt = [
|
||||
{"role": "system", "content": IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": "Here is the code of the app: code"},
|
||||
]
|
||||
assert tailwind_prompt == expected_tailwind_prompt
|
||||
|
||||
react_tailwind_prompt = assemble_imported_code_prompt(
|
||||
"code", "react_tailwind", "result_image_data_url"
|
||||
)
|
||||
expected_react_tailwind_prompt = [
|
||||
{"role": "system", "content": IMPORTED_CODE_REACT_TAILWIND_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": "Here is the code of the app: code"},
|
||||
]
|
||||
assert react_tailwind_prompt == expected_react_tailwind_prompt
|
||||
|
||||
bootstrap_prompt = assemble_imported_code_prompt(
|
||||
"code", "bootstrap", "result_image_data_url"
|
||||
)
|
||||
expected_bootstrap_prompt = [
|
||||
{"role": "system", "content": IMPORTED_CODE_BOOTSTRAP_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": "Here is the code of the app: code"},
|
||||
]
|
||||
assert bootstrap_prompt == expected_bootstrap_prompt
|
||||
|
||||
ionic_tailwind = assemble_imported_code_prompt(
|
||||
"code", "ionic_tailwind", "result_image_data_url"
|
||||
)
|
||||
expected_ionic_tailwind = [
|
||||
{"role": "system", "content": IMPORTED_CODE_IONIC_TAILWIND_SYSTEM_PROMPT},
|
||||
{"role": "user", "content": "Here is the code of the app: code"},
|
||||
]
|
||||
assert ionic_tailwind == expected_ionic_tailwind
|
||||
|
||||
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"},
|
||||
]
|
||||
assert svg == expected_svg
|
||||
@ -1,30 +0,0 @@
|
||||
import copy
|
||||
import json
|
||||
from typing import List
|
||||
from openai.types.chat import ChatCompletionMessageParam
|
||||
|
||||
|
||||
def pprint_prompt(prompt_messages: List[ChatCompletionMessageParam]):
|
||||
print(json.dumps(truncate_data_strings(prompt_messages), indent=4))
|
||||
|
||||
|
||||
def truncate_data_strings(data: List[ChatCompletionMessageParam]): # type: ignore
|
||||
# Deep clone the data to avoid modifying the original object
|
||||
cloned_data = copy.deepcopy(data)
|
||||
|
||||
if isinstance(cloned_data, dict):
|
||||
for key, value in cloned_data.items(): # type: ignore
|
||||
# Recursively call the function if the value is a dictionary or a list
|
||||
if isinstance(value, (dict, list)):
|
||||
cloned_data[key] = truncate_data_strings(value) # type: ignore
|
||||
# Truncate the string if it it's long and add ellipsis and length
|
||||
elif isinstance(value, str):
|
||||
cloned_data[key] = value[:40] # type: ignore
|
||||
if len(value) > 40:
|
||||
cloned_data[key] += "..." + f" ({len(value)} chars)" # type: ignore
|
||||
|
||||
elif isinstance(cloned_data, list): # type: ignore
|
||||
# Process each item in the list
|
||||
cloned_data = [truncate_data_strings(item) for item in cloned_data] # type: ignore
|
||||
|
||||
return cloned_data # type: ignore
|
||||
BIN
dist/screenshottocode-0.1.0-py3-none-any.whl
vendored
BIN
dist/screenshottocode-0.1.0-py3-none-any.whl
vendored
Binary file not shown.
BIN
dist/screenshottocode-0.1.0.tar.gz
vendored
BIN
dist/screenshottocode-0.1.0.tar.gz
vendored
Binary file not shown.
@ -24,8 +24,8 @@ export default ({ mode }) => {
|
||||
}),
|
||||
copy({
|
||||
targets: [
|
||||
{ src: 'dist/index.html', dest: path.resolve('../backend/templates/')},
|
||||
{ src: 'dist/assets', dest: path.resolve('../backend/') }
|
||||
{ src: 'dist/index.html', dest: path.resolve('../screenshottocode/templates/')},
|
||||
{ src: 'dist/assets', dest: path.resolve('../screenshottocode/') }
|
||||
]
|
||||
})
|
||||
],
|
||||
|
||||
@ -41,6 +41,3 @@ build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.poetry.scripts]
|
||||
screenshottocode = "screenshottocode.cli:main"
|
||||
|
||||
[tool.poetry.scripts.pre-build]
|
||||
build_frontend = "cd frontend && yarn build"
|
||||
@ -13,7 +13,7 @@ def start(args: Namespace):
|
||||
|
||||
Config.MODEL = args.model
|
||||
Config.IS_MODEL_GEMINI = args.model == 'gemini'
|
||||
|
||||
Config.API_KEY = args.api_key
|
||||
if args.production:
|
||||
Config.MODEL = args.production
|
||||
|
||||
@ -28,6 +28,7 @@ def get_arguments():
|
||||
|
||||
parser.add_argument('-m', '--model', default='gpt-4-vision', type=str, nargs='?', choices=['gpt-4-vision', 'gemini'],
|
||||
help='Name of Model to use')
|
||||
parser.add_argument('--api-key', default='', help='Set api key of selected model')
|
||||
parser.add_argument('--production', action='store_true', help='Run project in production mode')
|
||||
parser.add_argument('--host', type=str, default='127.0.0.1', help='Host address to run on')
|
||||
parser.add_argument('--port', type=int, default=7001, help='Host port to run on')
|
||||
|
||||
@ -8,7 +8,7 @@ class Config:
|
||||
MODEL = os.environ.get("MODEL", 'gpt-4-vision')
|
||||
IS_MODEL_GEMINI = MODEL == 'gemini'
|
||||
VERSION = '0.1.0'
|
||||
|
||||
API_KEY: str = ''
|
||||
# Set to True when running in production (on the hosted version)
|
||||
# Used as a feature flag to enable or disable certain features
|
||||
IS_PROD = os.environ.get("IS_PROD", False)
|
||||
|
||||
@ -63,11 +63,12 @@ async def stream_code(websocket: WebSocket):
|
||||
|
||||
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
|
||||
# If neither is provided, we throw an error.
|
||||
openai_api_key = os.getenv('OPENAI_API_KEY')
|
||||
openai_api_key = Config.API_KEY if not Config.IS_MODEL_GEMINI else None
|
||||
openai_base_url = None
|
||||
google_api_key = os.getenv('GOOGLE_API_KEY')
|
||||
model = 'model/gemini-pro-vision' if Config.IS_MODEL_GEMINI else 'gpt-4-vision'
|
||||
google_api_key = Config.API_KEY if Config.IS_MODEL_GEMINI else None
|
||||
|
||||
should_generate_images = False
|
||||
|
||||
if "accessCode" in params and params["accessCode"]:
|
||||
print("Access code - using platform API key")
|
||||
res = await validate_access_token(params["accessCode"])
|
||||
@ -85,20 +86,25 @@ async def stream_code(websocket: WebSocket):
|
||||
else:
|
||||
if params['model'] == 'models/gemini-pro-vision':
|
||||
print('Using the Gemini Pro Vision Model')
|
||||
model = params['model']
|
||||
|
||||
Config.MODEL = 'gemini'
|
||||
Config.IS_MODEL_GEMINI = True
|
||||
google_api_key = params['googleApiKey']
|
||||
if google_api_key:
|
||||
Config.API_KEY = google_api_key
|
||||
print("Using Google API key from client-side settings dialog")
|
||||
else:
|
||||
print('Using the GPT 4 Vision Model')
|
||||
if params["openAiApiKey"]:
|
||||
openai_api_key = params["openAiApiKey"]
|
||||
print("Using OpenAI API key from client-side settings dialog")
|
||||
if openai_api_key:
|
||||
Config.API_KEY = openai_api_key
|
||||
print("Using OpenAI API key from client-side settings dialog")
|
||||
else:
|
||||
if openai_api_key:
|
||||
print("Using OpenAI API key from environment variable")
|
||||
|
||||
if (model == 'gpt-4-vision' and not openai_api_key) or (model == 'models/gemini-pro-vision' and not google_api_key):
|
||||
if (not Config.IS_MODEL_GEMINI and not Config.API_KEY) or (Config.IS_MODEL_GEMINI and not Config.API_KEY):
|
||||
print("API key not found")
|
||||
await websocket.send_json(
|
||||
{
|
||||
@ -108,7 +114,7 @@ async def stream_code(websocket: WebSocket):
|
||||
)
|
||||
return
|
||||
|
||||
if model == 'gpt-4-vision':
|
||||
if not Config.IS_MODEL_GEMINI:
|
||||
# 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
|
||||
@ -198,20 +204,20 @@ async def stream_code(websocket: WebSocket):
|
||||
image_cache = create_alt_url_mapping(params["history"][-2])
|
||||
|
||||
# pprint_prompt(prompt_messages)
|
||||
|
||||
completion = ""
|
||||
if Config.SHOULD_MOCK_AI_RESPONSE:
|
||||
completion = await mock_completion(process_chunk)
|
||||
elif Config.IS_MODEL_GEMINI:
|
||||
elif Config.IS_MODEL_GEMINI and Config.API_KEY:
|
||||
completion = await stream_gemini_response(
|
||||
prompt_messages,
|
||||
api_key=google_api_key,
|
||||
api_key=Config.API_KEY,
|
||||
callback=lambda x: process_chunk(x),
|
||||
)
|
||||
else:
|
||||
elif Config.API_KEY:
|
||||
try:
|
||||
completion = await stream_openai_response(
|
||||
prompt_messages,
|
||||
api_key=openai_api_key,
|
||||
api_key=Config.API_KEY,
|
||||
base_url=openai_base_url,
|
||||
callback=lambda x: process_chunk(x),
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user