diff --git a/README.md b/README.md
index 076799c..393e027 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# screenshot-to-code
-This simple app converts a screenshot to HTML/Tailwind CSS. It uses GPT-4 Vision to generate the code and DALL-E 3 to generate similar-looking images. You can now also enter a URL to clone a live website!
+This simple app converts a screenshot to code (HTML/Tailwind CSS, or React or Vue or Bootstrap). It uses GPT-4 Vision to generate the code and DALL-E 3 to generate similar-looking images. You can now also enter a URL to clone a live website!
https://github.com/abi/screenshot-to-code/assets/23818/6cebadae-2fe3-4986-ac6a-8fb9db030045
@@ -8,17 +8,18 @@ See the [Examples](#examples) section below for more demos.
## 🚀 Try It Out!
-🆕 [Try it here](https://picoapps.xyz/free-tools/screenshot-to-code) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section below for details**). Or see [Getting Started](#-getting-started) below for local install instructions.
+🆕 [Try it here](https://screenshottocode.com) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section below for details**). Or see [Getting Started](#-getting-started) below for local install instructions.
## 🌟 Recent Updates
+- Nov 28 - 🔥 🔥 🔥 Get output code in React or Bootstrap or TailwindCSS
- Nov 23 - Send in a screenshot of the current replicated version (sometimes improves quality of subsequent generations)
- Nov 21 - Edit code in the code editor and preview changes live thanks to [@clean99](https://github.com/clean99)
- Nov 20 - Paste in a URL to screenshot and clone (requires [ScreenshotOne free API key](https://screenshotone.com?via=screenshot-to-code))
- Nov 19 - Support for dark/light code editor theme - thanks [@kachbit](https://github.com/kachbit)
- Nov 16 - Added a setting to disable DALL-E image generation if you don't need that
- Nov 16 - View code directly within the app
-- Nov 15 - 🔥 You can now instruct the AI to update the code as you wish. It is helpful if the AI messed up some styles or missed a section.
+- Nov 15 - You can now instruct the AI to update the code as you wish. It is helpful if the AI messed up some styles or missed a section.
## 🛠 Getting Started
@@ -87,6 +88,6 @@ https://github.com/abi/screenshot-to-code/assets/23818/3fec0f77-44e8-4fb3-a769-a
## 🌍 Hosted Version
-🆕 [Try it here](https://picoapps.xyz/free-tools/screenshot-to-code) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section for details**). Or see [Getting Started](#-getting-started) for local install instructions.
+🆕 [Try it here](https://screenshottocode.com) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section for details**). Or see [Getting Started](#-getting-started) for local install instructions.
[](https://www.buymeacoffee.com/abiraja)
diff --git a/backend/README.md b/backend/README.md
new file mode 100644
index 0000000..7c1cad2
--- /dev/null
+++ b/backend/README.md
@@ -0,0 +1,3 @@
+Run tests
+
+pytest test_prompts.py
diff --git a/backend/main.py b/backend/main.py
index e644760..4b75f71 100644
--- a/backend/main.py
+++ b/backend/main.py
@@ -69,6 +69,14 @@ async def stream_code(websocket: WebSocket):
print("Received params")
+ # Read the output settings from the request. Fall back to default if not provided.
+ output_settings = {"css": "tailwind", "js": "vanilla"}
+ if params["outputSettings"] and params["outputSettings"]["css"]:
+ output_settings["css"] = params["outputSettings"]["css"]
+ if params["outputSettings"] and params["outputSettings"]["js"]:
+ output_settings["js"] = params["outputSettings"]["js"]
+ print("Using output settings:", output_settings)
+
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
# If neither is provided, we throw an error.
if params["openAiApiKey"]:
@@ -102,9 +110,11 @@ async def stream_code(websocket: WebSocket):
await websocket.send_json({"type": "chunk", "value": content})
if params.get("resultImage") and params["resultImage"]:
- prompt_messages = assemble_prompt(params["image"], params["resultImage"])
+ prompt_messages = assemble_prompt(
+ params["image"], output_settings, params["resultImage"]
+ )
else:
- prompt_messages = assemble_prompt(params["image"])
+ prompt_messages = assemble_prompt(params["image"], output_settings)
# Image cache for updates so that we don't have to regenerate images
image_cache = {}
diff --git a/backend/prompts.py b/backend/prompts.py
index 1ba254d..f01eb7e 100644
--- a/backend/prompts.py
+++ b/backend/prompts.py
@@ -1,4 +1,4 @@
-SYSTEM_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.
@@ -23,12 +23,79 @@ Return only the full code in 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 "" and "" 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 "" 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:
+- You can use Google Fonts
+- Font Awesome for icons:
+
+Return only the full code in tags.
+Do not include markdown "```" or "```html" at the start or end.
+"""
+
+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 "" and "" 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 "" 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:
+
+
+
+- Use this script to include Tailwind:
+- You can use Google Fonts
+- Font Awesome for icons:
+
+Return only the full code in tags.
+Do not include markdown "```" or "```html" at the start or end.
+"""
+
USER_PROMPT = """
Generate code for a web page that looks exactly like this.
"""
-def assemble_prompt(image_data_url, result_image_data_url=None):
- content = [
+
+def assemble_prompt(image_data_url, output_settings: dict, result_image_data_url=None):
+ # Set the system prompt based on the output settings
+ chosen_prompt_name = "tailwind"
+ system_content = TAILWIND_SYSTEM_PROMPT
+ if output_settings["css"] == "bootstrap":
+ chosen_prompt_name = "bootstrap"
+ system_content = BOOTSTRAP_SYSTEM_PROMPT
+ if output_settings["js"] == "react":
+ chosen_prompt_name = "react-tailwind"
+ system_content = REACT_TAILWIND_SYSTEM_PROMPT
+
+ print("Using system prompt:", chosen_prompt_name)
+
+ user_content = [
{
"type": "image_url",
"image_url": {"url": image_data_url, "detail": "high"},
@@ -38,15 +105,23 @@ def assemble_prompt(image_data_url, result_image_data_url=None):
"text": USER_PROMPT,
},
]
+
+ # Include the result image if it exists
if result_image_data_url:
- content.insert(1, {
- "type": "image_url",
- "image_url": {"url": result_image_data_url, "detail": "high"},
- })
+ user_content.insert(
+ 1,
+ {
+ "type": "image_url",
+ "image_url": {"url": result_image_data_url, "detail": "high"},
+ },
+ )
return [
- {"role": "system", "content": SYSTEM_PROMPT},
+ {
+ "role": "system",
+ "content": system_content,
+ },
{
"role": "user",
- "content": content,
+ "content": user_content,
},
]
diff --git a/backend/test_prompts.py b/backend/test_prompts.py
new file mode 100644
index 0000000..2eaaaf4
--- /dev/null
+++ b/backend/test_prompts.py
@@ -0,0 +1,97 @@
+from prompts import 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 "" and "" 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 "" or bad things will happen.
+- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later.
+
+In terms of libraries,
+
+- Use this script to include Tailwind:
+- You can use Google Fonts
+- Font Awesome for icons:
+
+Return only the full code in tags.
+Do not include markdown "```" or "```html" at the start or end.
+"""
+
+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 "" and "" 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 "" 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:
+- You can use Google Fonts
+- Font Awesome for icons:
+
+Return only the full code in tags.
+Do not include markdown "```" or "```html" at the start or end.
+"""
+
+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 "" and "" 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 "" 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:
+
+
+
+- Use this script to include Tailwind:
+- You can use Google Fonts
+- Font Awesome for icons:
+
+Return only the full code in tags.
+Do not include markdown "```" or "```html" at the start or end.
+"""
+
+
+def test_prompts():
+ tailwind_prompt = assemble_prompt(
+ "image_data_url", {"css": "tailwind", "js": "vanilla"}, "result_image_data_url"
+ )
+ assert tailwind_prompt[0]["content"] == TAILWIND_SYSTEM_PROMPT
+
+ bootstrap_prompt = assemble_prompt(
+ "image_data_url", {"css": "bootstrap", "js": "vanilla"}, "result_image_data_url"
+ )
+ assert bootstrap_prompt[0]["content"] == BOOTSTRAP_SYSTEM_PROMPT
+
+ react_tailwind_prompt = assemble_prompt(
+ "image_data_url", {"css": "tailwind", "js": "react"}, "result_image_data_url"
+ )
+ assert react_tailwind_prompt[0]["content"] == REACT_TAILWIND_SYSTEM_PROMPT
diff --git a/frontend/package.json b/frontend/package.json
index 0fcc84f..0d180b6 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -13,11 +13,13 @@
},
"dependencies": {
"@codemirror/lang-html": "^6.4.6",
+ "@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-label": "^2.0.2",
+ "@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-separator": "^1.0.3",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-switch": "^1.0.3",
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 0ab18ec..d17e0fd 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -18,7 +18,14 @@ import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs";
import SettingsDialog from "./components/SettingsDialog";
-import { Settings, EditorTheme, AppState } from "./types";
+import {
+ Settings,
+ EditorTheme,
+ AppState,
+ CSSOption,
+ OutputSettings,
+ JSFrameworkOption,
+} from "./types";
import { IS_RUNNING_ON_CLOUD } from "./config";
import { PicoBadge } from "./components/PicoBadge";
import { OnboardingNote } from "./components/OnboardingNote";
@@ -28,6 +35,7 @@ import TermsOfServiceDialog from "./components/TermsOfServiceDialog";
import html2canvas from "html2canvas";
import { USER_CLOSE_WEB_SOCKET_CODE } from "./constants";
import CodeTab from "./components/CodeTab";
+import OutputSettingsSection from "./components/OutputSettingsSection";
function App() {
const [appState, setAppState] = useState(AppState.INITIAL);
@@ -46,8 +54,13 @@ function App() {
},
"setting"
);
+ const [outputSettings, setOutputSettings] = useState({
+ css: CSSOption.TAILWIND,
+ js: JSFrameworkOption.VANILLA,
+ });
const [shouldIncludeResultImage, setShouldIncludeResultImage] =
useState(false);
+
const wsRef = useRef(null);
const takeScreenshot = async (): Promise => {
@@ -99,7 +112,7 @@ function App() {
setAppState(AppState.CODING);
// Merge settings with params
- const updatedParams = { ...params, ...settings };
+ const updatedParams = { ...params, ...settings, outputSettings };
generateCode(
wsRef,
@@ -178,6 +191,13 @@ function App() {
)}
+ {appState === AppState.INITIAL && (
+
+ )}
+
{IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && }
{(appState === AppState.CODING ||
diff --git a/frontend/src/components/CodeMirror.tsx b/frontend/src/components/CodeMirror.tsx
index 1459dac..c1dba0e 100644
--- a/frontend/src/components/CodeMirror.tsx
+++ b/frontend/src/components/CodeMirror.tsx
@@ -22,28 +22,32 @@ interface Props {
function CodeMirror({ code, editorTheme, onCodeChange }: Props) {
const ref = useRef(null);
const view = useRef(null);
- const editorState = useMemo(() => EditorState.create({
- extensions: [
- history(),
- keymap.of([
- ...defaultKeymap,
- indentWithTab,
- { key: "Mod-z", run: undo, preventDefault: true },
- { key: "Mod-Shift-z", run: redo, preventDefault: true },
- ]),
- lineNumbers(),
- bracketMatching(),
- html(),
- editorTheme === EditorTheme.ESPRESSO ? espresso : cobalt,
- EditorView.lineWrapping,
- EditorView.updateListener.of((update: ViewUpdate) => {
- if (update.docChanged) {
- const updatedCode = update.state.doc.toString();
- onCodeChange(updatedCode);
- }
+ const editorState = useMemo(
+ () =>
+ EditorState.create({
+ extensions: [
+ history(),
+ keymap.of([
+ ...defaultKeymap,
+ indentWithTab,
+ { key: "Mod-z", run: undo, preventDefault: true },
+ { key: "Mod-Shift-z", run: redo, preventDefault: true },
+ ]),
+ lineNumbers(),
+ bracketMatching(),
+ html(),
+ editorTheme === EditorTheme.ESPRESSO ? espresso : cobalt,
+ EditorView.lineWrapping,
+ EditorView.updateListener.of((update: ViewUpdate) => {
+ if (update.docChanged) {
+ const updatedCode = update.state.doc.toString();
+ onCodeChange(updatedCode);
+ }
+ }),
+ ],
}),
- ],
- }), [editorTheme]);
+ [editorTheme]
+ );
useEffect(() => {
view.current = new EditorView({
state: editorState,
@@ -67,9 +71,11 @@ function CodeMirror({ code, editorTheme, onCodeChange }: Props) {
}, [code]);
return (
-
+
);
}
export default CodeMirror;
-
diff --git a/frontend/src/components/OutputSettingsSection.tsx b/frontend/src/components/OutputSettingsSection.tsx
new file mode 100644
index 0000000..fd76281
--- /dev/null
+++ b/frontend/src/components/OutputSettingsSection.tsx
@@ -0,0 +1,127 @@
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+} from "./ui/select";
+import { CSSOption, JSFrameworkOption, OutputSettings } from "../types";
+import {
+ Accordion,
+ AccordionContent,
+ AccordionItem,
+ AccordionTrigger,
+} from "./ui/accordion";
+import { capitalize } from "../lib/utils";
+import toast from "react-hot-toast";
+
+function displayCSSOption(option: CSSOption) {
+ switch (option) {
+ case CSSOption.TAILWIND:
+ return "Tailwind";
+ case CSSOption.BOOTSTRAP:
+ return "Bootstrap";
+ default:
+ return option;
+ }
+}
+
+function convertStringToCSSOption(option: string) {
+ switch (option) {
+ case "tailwind":
+ return CSSOption.TAILWIND;
+ case "bootstrap":
+ return CSSOption.BOOTSTRAP;
+ default:
+ throw new Error(`Unknown CSS option: ${option}`);
+ }
+}
+
+interface Props {
+ outputSettings: OutputSettings;
+ setOutputSettings: React.Dispatch>;
+}
+
+function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) {
+ const onCSSValueChange = (value: string) => {
+ setOutputSettings((prev) => {
+ if (prev.js === JSFrameworkOption.REACT) {
+ if (value !== CSSOption.TAILWIND) {
+ toast.error(
+ "React only supports Tailwind CSS. Change JS framework to Vanilla to use Bootstrap."
+ );
+ }
+ return {
+ css: CSSOption.TAILWIND,
+ js: JSFrameworkOption.REACT,
+ };
+ } else {
+ return {
+ ...prev,
+ css: convertStringToCSSOption(value),
+ };
+ }
+ });
+ };
+
+ const onJsFrameworkChange = (value: string) => {
+ if (value === JSFrameworkOption.REACT) {
+ setOutputSettings(() => ({
+ css: CSSOption.TAILWIND,
+ js: value as JSFrameworkOption,
+ }));
+ } else {
+ setOutputSettings((prev) => ({
+ ...prev,
+ js: value as JSFrameworkOption,
+ }));
+ }
+ };
+
+ return (
+
+
+
+