From 397fa0630f432c0b3802a5cdfdbafe822d5c1cbd Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 09:47:57 -0500 Subject: [PATCH 01/15] add support for outputs in bootstrap --- backend/main.py | 12 +- backend/prompts.py | 57 +++++- frontend/package.json | 1 + frontend/src/App.tsx | 22 ++- .../src/components/OutputSettingsSection.tsx | 63 +++++++ frontend/src/components/SettingsDialog.tsx | 17 +- frontend/src/components/ui/select.tsx | 174 ++++++++++++++++-- frontend/src/types.ts | 9 + frontend/yarn.lock | 110 +++++++++++ 9 files changed, 427 insertions(+), 38 deletions(-) create mode 100644 frontend/src/components/OutputSettingsSection.tsx diff --git a/backend/main.py b/backend/main.py index e644760..fc41cfd 100644 --- a/backend/main.py +++ b/backend/main.py @@ -69,6 +69,12 @@ 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"} + if params["outputSettings"] and params["outputSettings"]["css"]: + output_settings["css"] = params["outputSettings"]["css"] + 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 +108,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..7a4b426 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,43 @@ 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. +""" + 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 + system_content = TAILWIND_SYSTEM_PROMPT + if output_settings["css"] == "bootstrap": + system_content = BOOTSTRAP_SYSTEM_PROMPT + + user_content = [ { "type": "image_url", "image_url": {"url": image_data_url, "detail": "high"}, @@ -38,15 +69,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/frontend/package.json b/frontend/package.json index 0fcc84f..5c6fbb7 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "@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 b34c8bc..b40d8a9 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -18,7 +18,13 @@ 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, +} from "./types"; import { IS_RUNNING_ON_CLOUD } from "./config"; import { PicoBadge } from "./components/PicoBadge"; import { OnboardingNote } from "./components/OnboardingNote"; @@ -28,6 +34,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 +53,12 @@ function App() { }, "setting" ); + const [outputSettings, setOutputSettings] = useState({ + css: CSSOption.TAILWIND, + }); const [shouldIncludeResultImage, setShouldIncludeResultImage] = useState(false); + const wsRef = useRef(null); const takeScreenshot = async (): Promise => { @@ -99,7 +110,7 @@ function App() { setAppState(AppState.CODING); // Merge settings with params - const updatedParams = { ...params, ...settings }; + const updatedParams = { ...params, ...settings, outputSettings }; generateCode( wsRef, @@ -178,6 +189,13 @@ function App() { )} + {appState === AppState.INITIAL && ( + + )} + {IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && } {(appState === AppState.CODING || diff --git a/frontend/src/components/OutputSettingsSection.tsx b/frontend/src/components/OutputSettingsSection.tsx new file mode 100644 index 0000000..730a8f2 --- /dev/null +++ b/frontend/src/components/OutputSettingsSection.tsx @@ -0,0 +1,63 @@ +import { + Select, + SelectContent, + SelectGroup, + SelectItem, + SelectTrigger, +} from "./ui/select"; +import { CSSOption, OutputSettings } from "../types"; + +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: (outputSettings: OutputSettings) => void; +} + +function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { + return ( +
+ CSS + +
+ ); +} + +export default OutputSettingsSection; diff --git a/frontend/src/components/SettingsDialog.tsx b/frontend/src/components/SettingsDialog.tsx index 397ce02..bddd444 100644 --- a/frontend/src/components/SettingsDialog.tsx +++ b/frontend/src/components/SettingsDialog.tsx @@ -13,7 +13,7 @@ import { EditorTheme, Settings } from "../types"; import { Switch } from "./ui/switch"; import { Label } from "./ui/label"; import { Input } from "./ui/input"; -import { Select } from "./ui/select"; +import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select"; interface Props { settings: Settings; @@ -110,14 +110,17 @@ function SettingsDialog({ settings, setSettings }: Props) {
diff --git a/frontend/src/components/ui/select.tsx b/frontend/src/components/ui/select.tsx index 6d4d943..cdfb8ce 100644 --- a/frontend/src/components/ui/select.tsx +++ b/frontend/src/components/ui/select.tsx @@ -1,24 +1,162 @@ -import * as React from "react"; -import { cn } from "@/lib/utils"; +import * as React from "react" +import { + CaretSortIcon, + CheckIcon, + ChevronDownIcon, + ChevronUpIcon, +} from "@radix-ui/react-icons" +import * as SelectPrimitive from "@radix-ui/react-select" -export interface SelectProps - extends React.SelectHTMLAttributes {} +import { cn } from "@/lib/utils" -const Select = React.forwardRef( - ({ className, ...props }, ref) => { - return ( - - setOutputSettings({ - css: convertStringToCSSOption(value), - }) - } - > - - {displayCSSOption(outputSettings.css)} - - - - Tailwind - Bootstrap - - - - + + + Output Settings + +
+ CSS + +
+
+
+
); } diff --git a/frontend/src/components/ui/accordion.tsx b/frontend/src/components/ui/accordion.tsx new file mode 100644 index 0000000..7e84c32 --- /dev/null +++ b/frontend/src/components/ui/accordion.tsx @@ -0,0 +1,55 @@ +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDownIcon } from "@radix-ui/react-icons" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0492c28..6edff02 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -635,6 +635,22 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-accordion@^1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-accordion/-/react-accordion-1.1.2.tgz#738441f7343e5142273cdef94d12054c3287966f" + integrity sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-collapsible" "1.0.3" + "@radix-ui/react-collection" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-alert-dialog@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.5.tgz#70dd529cbf1e4bff386814d3776901fcaa131b8c" @@ -671,6 +687,21 @@ "@radix-ui/react-use-previous" "1.0.1" "@radix-ui/react-use-size" "1.0.1" +"@radix-ui/react-collapsible@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz#df0e22e7a025439f13f62d4e4a9e92c4a0df5b81" + integrity sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-collection@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159" From d6b3bf8573bd3603a7ab6ddeb1703428f658ac1a Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 12:23:08 -0500 Subject: [PATCH 05/15] add unit tests for prompts --- backend/README.md | 3 ++ backend/test_prompts.py | 63 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 backend/README.md create mode 100644 backend/test_prompts.py 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/test_prompts.py b/backend/test_prompts.py new file mode 100644 index 0000000..f6c9526 --- /dev/null +++ b/backend/test_prompts.py @@ -0,0 +1,63 @@ +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. +""" + + +def test_prompts(): + tailwind_prompt = assemble_prompt( + "image_data_url", {"css": "tailwind"}, "result_image_data_url" + ) + assert tailwind_prompt[0]["content"] == TAILWIND_SYSTEM_PROMPT + + bootstrap_prompt = assemble_prompt( + "image_data_url", {"css": "bootstrap"}, "result_image_data_url" + ) + assert bootstrap_prompt[0]["content"] == BOOTSTRAP_SYSTEM_PROMPT From dfe8ae4164ec1a2633e03ee9823c45dbd783082f Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 14:05:59 -0500 Subject: [PATCH 06/15] add support for React --- backend/main.py | 4 +- backend/prompts.py | 31 ++++++++ backend/test_prompts.py | 38 +++++++++- frontend/src/App.tsx | 2 + .../src/components/OutputSettingsSection.tsx | 73 ++++++++++++++++--- frontend/src/types.ts | 7 ++ 6 files changed, 140 insertions(+), 15 deletions(-) diff --git a/backend/main.py b/backend/main.py index fc41cfd..4b75f71 100644 --- a/backend/main.py +++ b/backend/main.py @@ -70,9 +70,11 @@ 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"} + 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. diff --git a/backend/prompts.py b/backend/prompts.py index 7a4b426..18dde89 100644 --- a/backend/prompts.py +++ b/backend/prompts.py @@ -48,6 +48,35 @@ 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. """ @@ -58,6 +87,8 @@ def assemble_prompt(image_data_url, output_settings: dict, result_image_data_url system_content = TAILWIND_SYSTEM_PROMPT if output_settings["css"] == "bootstrap": system_content = BOOTSTRAP_SYSTEM_PROMPT + if output_settings["js"] == "react": + system_content = REACT_TAILWIND_SYSTEM_PROMPT user_content = [ { diff --git a/backend/test_prompts.py b/backend/test_prompts.py index f6c9526..2eaaaf4 100644 --- a/backend/test_prompts.py +++ b/backend/test_prompts.py @@ -50,14 +50,48 @@ 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"}, "result_image_data_url" + "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"}, "result_image_data_url" + "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/src/App.tsx b/frontend/src/App.tsx index b40d8a9..5f65f96 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -24,6 +24,7 @@ import { AppState, CSSOption, OutputSettings, + JSFrameworkOption, } from "./types"; import { IS_RUNNING_ON_CLOUD } from "./config"; import { PicoBadge } from "./components/PicoBadge"; @@ -55,6 +56,7 @@ function App() { ); const [outputSettings, setOutputSettings] = useState({ css: CSSOption.TAILWIND, + js: JSFrameworkOption.VANILLA, }); const [shouldIncludeResultImage, setShouldIncludeResultImage] = useState(false); diff --git a/frontend/src/components/OutputSettingsSection.tsx b/frontend/src/components/OutputSettingsSection.tsx index 5806c34..32f9979 100644 --- a/frontend/src/components/OutputSettingsSection.tsx +++ b/frontend/src/components/OutputSettingsSection.tsx @@ -5,13 +5,15 @@ import { SelectItem, SelectTrigger, } from "./ui/select"; -import { CSSOption, OutputSettings } from "../types"; +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) { @@ -37,25 +39,53 @@ function convertStringToCSSOption(option: string) { interface Props { outputSettings: OutputSettings; - setOutputSettings: (outputSettings: OutputSettings) => void; + 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 ( Output Settings - -
+ +
CSS - {displayCSSOption(outputSettings.css)} @@ -67,6 +97,25 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) {
+
+ JS Framework + +
diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 9c30d0b..964ad5a 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -8,8 +8,15 @@ export enum CSSOption { BOOTSTRAP = "bootstrap", } +export enum JSFrameworkOption { + VANILLA = "vanilla", + REACT = "react", + VUE = "vue", +} + export interface OutputSettings { css: CSSOption; + js: JSFrameworkOption; } export interface Settings { From 0b8c638921da688b6926952ddcc684c8ae0d0b02 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 14:07:09 -0500 Subject: [PATCH 07/15] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 076799c..1e26769 100644 --- a/README.md +++ b/README.md @@ -12,13 +12,14 @@ See the [Examples](#examples) section below for more demos. ## 🌟 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 From 7313b30ab573f60286a92aab6d68568f6ac29f20 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 14:07:49 -0500 Subject: [PATCH 08/15] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e26769..cc98a73 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 From 8c08d992fd35b688c0a6722808b6e2ea285130cd Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 14:33:03 -0500 Subject: [PATCH 09/15] print system prompt info in the backend logs --- backend/prompts.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/backend/prompts.py b/backend/prompts.py index 18dde89..f01eb7e 100644 --- a/backend/prompts.py +++ b/backend/prompts.py @@ -84,12 +84,17 @@ Generate code for a web page that looks exactly like this. 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", From 541e662ae0a2afcd6055d16831e1f76fe30156d7 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 15:20:35 -0500 Subject: [PATCH 10/15] disable output settings changes on the hosted version for now --- .../src/components/OutputSettingsSection.tsx | 19 +++++++++- frontend/src/components/ui/badge.tsx | 36 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 frontend/src/components/ui/badge.tsx diff --git a/frontend/src/components/OutputSettingsSection.tsx b/frontend/src/components/OutputSettingsSection.tsx index 32f9979..2d2dfa9 100644 --- a/frontend/src/components/OutputSettingsSection.tsx +++ b/frontend/src/components/OutputSettingsSection.tsx @@ -14,6 +14,8 @@ import { } from "./ui/accordion"; import { capitalize } from "../lib/utils"; import toast from "react-hot-toast"; +import { IS_RUNNING_ON_CLOUD } from "../config"; +import { Badge } from "./ui/badge"; function displayCSSOption(option: CSSOption) { switch (option) { @@ -44,6 +46,11 @@ interface Props { function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { const onCSSValueChange = (value: string) => { + if (IS_RUNNING_ON_CLOUD) { + toast.error("Upgrade to the Business plan to change CSS framework."); + return; + } + setOutputSettings((prev) => { if (prev.js === JSFrameworkOption.REACT) { if (value !== CSSOption.TAILWIND) { @@ -65,6 +72,11 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { }; const onJsFrameworkChange = (value: string) => { + if (IS_RUNNING_ON_CLOUD) { + toast.error("Upgrade to the Business plan to change JS framework."); + return; + } + if (value === JSFrameworkOption.REACT) { setOutputSettings(() => ({ css: CSSOption.TAILWIND, @@ -81,7 +93,12 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { return ( - Output Settings + +
+ Output Settings{" "} + {IS_RUNNING_ON_CLOUD && Pro} +
+
CSS diff --git a/frontend/src/components/ui/badge.tsx b/frontend/src/components/ui/badge.tsx new file mode 100644 index 0000000..e87d62b --- /dev/null +++ b/frontend/src/components/ui/badge.tsx @@ -0,0 +1,36 @@ +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } From 2ba81690bf50929f786c51bca2aa97a177d612e1 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 16:58:29 -0500 Subject: [PATCH 11/15] update base to deploy at root --- frontend/vite.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 37bae62..2ea6a53 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -8,7 +8,7 @@ import { createHtmlPlugin } from "vite-plugin-html"; export default ({ mode }) => { process.env = { ...process.env, ...loadEnv(mode, process.cwd()) }; return defineConfig({ - base: process.env.VITE_IS_DEPLOYED ? "/free-tools/screenshot-to-code/" : "", + base: "", plugins: [ react(), checker({ typescript: true }), From ea71c985896c4d5510205b5f5739f3130ee91876 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 17:13:00 -0500 Subject: [PATCH 12/15] inject plausible script on hosted version --- frontend/vite.config.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 2ea6a53..f9b5353 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -15,7 +15,9 @@ export default ({ mode }) => { createHtmlPlugin({ inject: { data: { - injectHead: process.env.VITE_INJECT_HEAD, + injectHead: process.env.VITE_IS_DEPLOYED + ? '' + : "", }, }, }), From 0f3ba824a3c2d17040ed361cd3fcee2521e25034 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 17:32:35 -0500 Subject: [PATCH 13/15] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cc98a73..393e027 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ 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 @@ -88,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. [!["Buy Me A Coffee"](https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png)](https://www.buymeacoffee.com/abiraja) From 5a91292dcd756486968fe31a383f11a610fe9331 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 17:48:02 -0500 Subject: [PATCH 14/15] update copy --- frontend/src/components/PicoBadge.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/components/PicoBadge.tsx b/frontend/src/components/PicoBadge.tsx index b5a0af9..bdd0083 100644 --- a/frontend/src/components/PicoBadge.tsx +++ b/frontend/src/components/PicoBadge.tsx @@ -9,7 +9,7 @@ export function PicoBadge() { className="fixed z-50 bottom-16 right-5 rounded-md shadow bg-black text-white px-4 text-xs py-3 cursor-pointer" > - feedback? + feature requests?
From a3b8a78a8dd11716c966031501b0884cd9004288 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 20:37:05 -0500 Subject: [PATCH 15/15] enable output settings on hosted version --- .../src/components/OutputSettingsSection.tsx | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/frontend/src/components/OutputSettingsSection.tsx b/frontend/src/components/OutputSettingsSection.tsx index 2d2dfa9..fd76281 100644 --- a/frontend/src/components/OutputSettingsSection.tsx +++ b/frontend/src/components/OutputSettingsSection.tsx @@ -14,8 +14,6 @@ import { } from "./ui/accordion"; import { capitalize } from "../lib/utils"; import toast from "react-hot-toast"; -import { IS_RUNNING_ON_CLOUD } from "../config"; -import { Badge } from "./ui/badge"; function displayCSSOption(option: CSSOption) { switch (option) { @@ -46,11 +44,6 @@ interface Props { function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { const onCSSValueChange = (value: string) => { - if (IS_RUNNING_ON_CLOUD) { - toast.error("Upgrade to the Business plan to change CSS framework."); - return; - } - setOutputSettings((prev) => { if (prev.js === JSFrameworkOption.REACT) { if (value !== CSSOption.TAILWIND) { @@ -72,11 +65,6 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { }; const onJsFrameworkChange = (value: string) => { - if (IS_RUNNING_ON_CLOUD) { - toast.error("Upgrade to the Business plan to change JS framework."); - return; - } - if (value === JSFrameworkOption.REACT) { setOutputSettings(() => ({ css: CSSOption.TAILWIND, @@ -94,10 +82,7 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { -
- Output Settings{" "} - {IS_RUNNING_ON_CLOUD && Pro} -
+
Output Settings