From 9bee5c79b8d41d9b193c7cc76efdd3b6d8cf54ba Mon Sep 17 00:00:00 2001 From: dialmedu Date: Tue, 28 Nov 2023 23:50:04 -0500 Subject: [PATCH 1/6] Add prompt ionic support --- backend/main.py | 4 +++- backend/prompts.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/backend/main.py b/backend/main.py index 4b75f71..b8e3f4f 100644 --- a/backend/main.py +++ b/backend/main.py @@ -70,11 +70,13 @@ 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"} + output_settings = {"css": "tailwind", "js": "vanilla" , "components" : "html"} 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"] + if params["outputSettings"] and params["outputSettings"]["components"]: + output_settings["components"] = params["outputSettings"]["components"] 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 f01eb7e..fe3ba9e 100644 --- a/backend/prompts.py +++ b/backend/prompts.py @@ -77,6 +77,40 @@ Return only the full code in 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 "" 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 Ionic so that it can run on a standalone page: + + + +- Use this script to include Tailwind: +- You can use Google Fonts +- ionicons for icons, add the following + + + +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. """ @@ -92,6 +126,9 @@ def assemble_prompt(image_data_url, output_settings: dict, result_image_data_url if output_settings["js"] == "react": chosen_prompt_name = "react-tailwind" system_content = REACT_TAILWIND_SYSTEM_PROMPT + if output_settings["components"] == "ionic": + chosen_prompt_name = "ionic-tailwind" + system_content = IONIC_TAILWIND_SYSTEM_PROMPT print("Using system prompt:", chosen_prompt_name) From e13840d8e97ca0a497ec3ffca88a40dbe8699d28 Mon Sep 17 00:00:00 2001 From: dialmedu Date: Tue, 28 Nov 2023 23:56:16 -0500 Subject: [PATCH 2/6] Add Ionic support and Check Options --- frontend/src/App.tsx | 2 + .../src/components/OutputSettingsSection.tsx | 106 +++++++++++++++++- frontend/src/types.ts | 6 + 3 files changed, 112 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5f65f96..1cc4588 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -25,6 +25,7 @@ import { CSSOption, OutputSettings, JSFrameworkOption, + UIComponentOption, } from "./types"; import { IS_RUNNING_ON_CLOUD } from "./config"; import { PicoBadge } from "./components/PicoBadge"; @@ -57,6 +58,7 @@ function App() { const [outputSettings, setOutputSettings] = useState({ css: CSSOption.TAILWIND, js: JSFrameworkOption.VANILLA, + components: UIComponentOption.HTML, }); const [shouldIncludeResultImage, setShouldIncludeResultImage] = useState(false); diff --git a/frontend/src/components/OutputSettingsSection.tsx b/frontend/src/components/OutputSettingsSection.tsx index 2d2dfa9..7f29069 100644 --- a/frontend/src/components/OutputSettingsSection.tsx +++ b/frontend/src/components/OutputSettingsSection.tsx @@ -5,7 +5,7 @@ import { SelectItem, SelectTrigger, } from "./ui/select"; -import { CSSOption, JSFrameworkOption, OutputSettings } from "../types"; +import { CSSOption, UIComponentOption, JSFrameworkOption, OutputSettings } from "../types"; import { Accordion, AccordionContent, @@ -16,6 +16,7 @@ import { capitalize } from "../lib/utils"; import toast from "react-hot-toast"; import { IS_RUNNING_ON_CLOUD } from "../config"; import { Badge } from "./ui/badge"; +import { useEffect } from "react"; function displayCSSOption(option: CSSOption) { switch (option) { @@ -61,6 +62,7 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { return { css: CSSOption.TAILWIND, js: JSFrameworkOption.REACT, + components: UIComponentOption.HTML }; } else { return { @@ -81,6 +83,7 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { setOutputSettings(() => ({ css: CSSOption.TAILWIND, js: value as JSFrameworkOption, + components: UIComponentOption.HTML })); } else { setOutputSettings((prev) => ({ @@ -90,6 +93,85 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { } }; + const onUIComponentOptionChange = (value: string) => { + if (IS_RUNNING_ON_CLOUD) { + toast.error("Upgrade to the Business plan to change Components library."); + return; + } + + if (value === UIComponentOption.IONIC) { + setOutputSettings(() => ({ + css: CSSOption.TAILWIND, + js: JSFrameworkOption.VANILLA, + components: value as UIComponentOption + })); + } else { + setOutputSettings((prev) => ({ + ...prev, + components: value as UIComponentOption, + })); + } + }; + + + const checkUIComponentOptionOrDefault = (valueItem: UIComponentOption ) : UIComponentOption => { + switch (valueItem) { + case UIComponentOption.IONIC: + if (outputSettings.js != JSFrameworkOption.VANILLA || outputSettings.css != CSSOption.TAILWIND) { + return UIComponentOption.HTML + } + } + return valueItem; + } + + const checkCSSOptionOrDefault = (valueItem: CSSOption ) : CSSOption => { + switch (valueItem) { + default: + return valueItem; + } + } + + const checkJSFrameworkOptionOrDefault = (valueItem: JSFrameworkOption ) : JSFrameworkOption => { + switch (valueItem) { + case JSFrameworkOption.REACT: + if(outputSettings.css != CSSOption.TAILWIND) { + return JSFrameworkOption.VANILLA + } + break; + } + return valueItem; + } + + useEffect(() => { + checkOutputSettingsOptions(); + }, [outputSettings]); + + const checkOutputSettingsOptions = () => { + if ( isHiddenOption(outputSettings.css) || isHiddenOption(outputSettings.js) || isHiddenOption(outputSettings.components)) + { + setOutputSettings((prev) => { + return { + css: checkCSSOptionOrDefault(prev.css), + js: checkJSFrameworkOptionOrDefault(prev.js), + components: checkUIComponentOptionOrDefault(prev.components), + }; + }) + } + }; + + const isHiddenOption = ( option : CSSOption| JSFrameworkOption | UIComponentOption ) : boolean => { + if (Object.values(CSSOption).includes(option as CSSOption)){ + return checkCSSOptionOrDefault(option as CSSOption) != option + } + if (Object.values(JSFrameworkOption).includes(option as JSFrameworkOption)){ + return checkJSFrameworkOptionOrDefault(option as JSFrameworkOption) != option + } + if (Object.values(UIComponentOption).includes(option as UIComponentOption)){ + return checkUIComponentOptionOrDefault(option as UIComponentOption) != option + } + return true + } + return ( @@ -128,11 +210,31 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { Vanilla - React + React +
+ Component Library + +
+
+ Output: {outputSettings.js} + {outputSettings.css} + {outputSettings.components} +
diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 964ad5a..0963f32 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -14,9 +14,15 @@ export enum JSFrameworkOption { VUE = "vue", } +export enum UIComponentOption { + HTML = 'HTML', + IONIC = 'ionic' +} + export interface OutputSettings { css: CSSOption; js: JSFrameworkOption; + components: UIComponentOption; } export interface Settings { From 7e504ebbdd0b7c38deae9d41229b40af776ae089 Mon Sep 17 00:00:00 2001 From: dialmedu Date: Wed, 29 Nov 2023 00:09:23 -0500 Subject: [PATCH 3/6] add select UI Component and check options --- frontend/src/App.tsx | 2 + .../src/components/OutputSettingsSection.tsx | 101 +++++++++++++++++- frontend/src/types.ts | 6 ++ 3 files changed, 107 insertions(+), 2 deletions(-) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0941a8b..060a6f3 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -25,6 +25,7 @@ import { CSSOption, OutputSettings, JSFrameworkOption, + UIComponentOption, } from "./types"; import { IS_RUNNING_ON_CLOUD } from "./config"; import { PicoBadge } from "./components/PicoBadge"; @@ -57,6 +58,7 @@ function App() { const [outputSettings, setOutputSettings] = useState({ css: CSSOption.TAILWIND, js: JSFrameworkOption.VANILLA, + components: UIComponentOption.HTML, }); const [shouldIncludeResultImage, setShouldIncludeResultImage] = useState(false); diff --git a/frontend/src/components/OutputSettingsSection.tsx b/frontend/src/components/OutputSettingsSection.tsx index fd76281..8f4200a 100644 --- a/frontend/src/components/OutputSettingsSection.tsx +++ b/frontend/src/components/OutputSettingsSection.tsx @@ -5,7 +5,7 @@ import { SelectItem, SelectTrigger, } from "./ui/select"; -import { CSSOption, JSFrameworkOption, OutputSettings } from "../types"; +import { CSSOption, UIComponentOption, JSFrameworkOption, OutputSettings } from "../types"; import { Accordion, AccordionContent, @@ -14,6 +14,7 @@ import { } from "./ui/accordion"; import { capitalize } from "../lib/utils"; import toast from "react-hot-toast"; +import { useEffect } from "react"; function displayCSSOption(option: CSSOption) { switch (option) { @@ -54,6 +55,7 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { return { css: CSSOption.TAILWIND, js: JSFrameworkOption.REACT, + components: UIComponentOption.HTML }; } else { return { @@ -69,6 +71,7 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { setOutputSettings(() => ({ css: CSSOption.TAILWIND, js: value as JSFrameworkOption, + components: UIComponentOption.HTML })); } else { setOutputSettings((prev) => ({ @@ -78,6 +81,80 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { } }; + const onUIComponentOptionChange = (value: string) => { + if (value === UIComponentOption.IONIC) { + setOutputSettings(() => ({ + css: CSSOption.TAILWIND, + js: JSFrameworkOption.VANILLA, + components: value as UIComponentOption + })); + } else { + setOutputSettings((prev) => ({ + ...prev, + components: value as UIComponentOption, + })); + } + }; + + + const checkUIComponentOptionOrDefault = (valueItem: UIComponentOption ) : UIComponentOption => { + switch (valueItem) { + case UIComponentOption.IONIC: + if (outputSettings.js != JSFrameworkOption.VANILLA || outputSettings.css != CSSOption.TAILWIND) { + return UIComponentOption.HTML + } + } + return valueItem; + } + + const checkCSSOptionOrDefault = (valueItem: CSSOption ) : CSSOption => { + switch (valueItem) { + default: + return valueItem; + } + } + + const checkJSFrameworkOptionOrDefault = (valueItem: JSFrameworkOption ) : JSFrameworkOption => { + switch (valueItem) { + case JSFrameworkOption.REACT: + if(outputSettings.css != CSSOption.TAILWIND) { + return JSFrameworkOption.VANILLA + } + break; + } + return valueItem; + } + + useEffect(() => { + checkOutputSettingsOptions(); + }, [outputSettings]); + + const checkOutputSettingsOptions = () => { + if ( isHiddenOption(outputSettings.css) || isHiddenOption(outputSettings.js) || isHiddenOption(outputSettings.components)) + { + setOutputSettings((prev) => { + return { + css: checkCSSOptionOrDefault(prev.css), + js: checkJSFrameworkOptionOrDefault(prev.js), + components: checkUIComponentOptionOrDefault(prev.components), + }; + }) + } + }; + + const isHiddenOption = ( option : CSSOption| JSFrameworkOption | UIComponentOption ) : boolean => { + if (Object.values(CSSOption).includes(option as CSSOption)){ + return checkCSSOptionOrDefault(option as CSSOption) != option + } + if (Object.values(JSFrameworkOption).includes(option as JSFrameworkOption)){ + return checkJSFrameworkOptionOrDefault(option as JSFrameworkOption) != option + } + if (Object.values(UIComponentOption).includes(option as UIComponentOption)){ + return checkUIComponentOptionOrDefault(option as UIComponentOption) != option + } + return true + } + return ( @@ -113,11 +190,31 @@ function OutputSettingsSection({ outputSettings, setOutputSettings }: Props) { Vanilla - React + React +
+ Component Library + +
+
+ Output: {outputSettings.js} + {outputSettings.css} + {outputSettings.components} +
diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 964ad5a..0963f32 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -14,9 +14,15 @@ export enum JSFrameworkOption { VUE = "vue", } +export enum UIComponentOption { + HTML = 'HTML', + IONIC = 'ionic' +} + export interface OutputSettings { css: CSSOption; js: JSFrameworkOption; + components: UIComponentOption; } export interface Settings { From f51e70d701fbefb3b8dd71477cf15a1069749ced Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 30 Nov 2023 15:58:01 -0500 Subject: [PATCH 4/6] redo how output settings is configured --- backend/main.py | 18 +- backend/prompts.py | 22 +- frontend/src/App.tsx | 43 +- .../src/components/OutputSettingsSection.tsx | 370 ++++-------------- frontend/src/types.ts | 30 +- 5 files changed, 118 insertions(+), 365 deletions(-) diff --git a/backend/main.py b/backend/main.py index 2808a6d..eac04b0 100644 --- a/backend/main.py +++ b/backend/main.py @@ -69,15 +69,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", "js": "vanilla" , "components" : "html"} - 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"] - if params["outputSettings"] and params["outputSettings"]["components"]: - output_settings["components"] = params["outputSettings"]["components"] - print("Using output settings:", output_settings) + # 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. @@ -143,10 +139,10 @@ async def stream_code(websocket: WebSocket): if params.get("resultImage") and params["resultImage"]: prompt_messages = assemble_prompt( - params["image"], output_settings, params["resultImage"] + params["image"], generated_code_config, params["resultImage"] ) else: - prompt_messages = assemble_prompt(params["image"], output_settings) + prompt_messages = assemble_prompt(params["image"], generated_code_config) # 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 fe3ba9e..c9e48cb 100644 --- a/backend/prompts.py +++ b/backend/prompts.py @@ -116,21 +116,21 @@ 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): +def assemble_prompt( + image_data_url, generated_code_config: str, 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" + if generated_code_config == "html_tailwind": + system_content = TAILWIND_SYSTEM_PROMPT + elif generated_code_config == "react_tailwind": system_content = REACT_TAILWIND_SYSTEM_PROMPT - if output_settings["components"] == "ionic": - chosen_prompt_name = "ionic-tailwind" + elif generated_code_config == "bootstrap": + system_content = BOOTSTRAP_SYSTEM_PROMPT + elif generated_code_config == "ionic_tailwind": system_content = IONIC_TAILWIND_SYSTEM_PROMPT - - print("Using system prompt:", chosen_prompt_name) + else: + raise Exception("Code config is not one of available options") user_content = [ { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 58e07e4..1c7f131 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useRef, useState } from "react"; +import { useEffect, useRef, useState } from "react"; import ImageUpload from "./components/ImageUpload"; import CodePreview from "./components/CodePreview"; import Preview from "./components/Preview"; @@ -18,15 +18,7 @@ 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, - CSSOption, - OutputSettings, - JSFrameworkOption, - UIComponentOption, -} from "./types"; +import { Settings, EditorTheme, AppState, GeneratedCodeConfig } from "./types"; import { IS_RUNNING_ON_CLOUD } from "./config"; import { PicoBadge } from "./components/PicoBadge"; import { OnboardingNote } from "./components/OnboardingNote"; @@ -52,21 +44,31 @@ function App() { screenshotOneApiKey: null, isImageGenerationEnabled: true, editorTheme: EditorTheme.COBALT, + generatedCodeConfig: GeneratedCodeConfig.HTML_TAILWIND, + // Only relevant for hosted version isTermOfServiceAccepted: false, accessCode: null, }, "setting" ); - const [outputSettings, setOutputSettings] = useState({ - css: CSSOption.TAILWIND, - components: UIComponentOption.HTML, - js: JSFrameworkOption.NO_FRAMEWORK, - }); + const [shouldIncludeResultImage, setShouldIncludeResultImage] = useState(false); const wsRef = useRef(null); + // When the user already has the settings in local storage, newly added keys + // do not get added to the settings so if it's falsy, we populate it with the default + // value + useEffect(() => { + if (!settings.generatedCodeConfig) { + setSettings((prev) => ({ + ...prev, + generatedCodeConfig: GeneratedCodeConfig.HTML_TAILWIND, + })); + } + }, [settings.generatedCodeConfig, setSettings]); + const takeScreenshot = async (): Promise => { const iframeElement = document.querySelector( "#preview-desktop" @@ -116,7 +118,7 @@ function App() { setAppState(AppState.CODING); // Merge settings with params - const updatedParams = { ...params, ...settings, outputSettings }; + const updatedParams = { ...params, ...settings }; generateCode( wsRef, @@ -190,8 +192,13 @@ function App() { + setSettings((prev) => ({ + ...prev, + generatedCodeConfig: config, + })) + } shouldDisableUpdates={ appState === AppState.CODING || appState === AppState.CODE_READY } diff --git a/frontend/src/components/OutputSettingsSection.tsx b/frontend/src/components/OutputSettingsSection.tsx index 9b73405..69d1b0a 100644 --- a/frontend/src/components/OutputSettingsSection.tsx +++ b/frontend/src/components/OutputSettingsSection.tsx @@ -5,324 +5,86 @@ import { SelectItem, SelectTrigger, } from "./ui/select"; -import { - CSSOption, - UIComponentOption, - JSFrameworkOption, - OutputSettings, -} from "../types"; -import { capitalize } from "../lib/utils"; -import toast from "react-hot-toast"; -import { useEffect } from "react"; -import { Label } from "@radix-ui/react-label"; -import { Button } from "./ui/button"; -import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover"; +import { GeneratedCodeConfig } from "../types"; -function displayCSSOption(option: CSSOption) { - switch (option) { - case CSSOption.TAILWIND: - return "Tailwind"; - case CSSOption.BOOTSTRAP: - return "Bootstrap"; +function generateDisplayComponent(config: GeneratedCodeConfig) { + switch (config) { + case GeneratedCodeConfig.HTML_TAILWIND: + return ( +
+ HTML +{" "} + Tailwind +
+ ); + case GeneratedCodeConfig.REACT_TAILWIND: + return ( +
+ React +{" "} + Tailwind +
+ ); + case GeneratedCodeConfig.BOOTSTRAP: + return ( +
+ Bootstrap +
+ ); + case GeneratedCodeConfig.IONIC_TAILWIND: + return ( +
+ Ionic +{" "} + Tailwind +
+ ); default: - return option; - } -} - -function displayJSOption(option: JSFrameworkOption) { - switch (option) { - case JSFrameworkOption.REACT: - return "React"; - case JSFrameworkOption.NO_FRAMEWORK: - return "No Framework"; - 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}`); - } -} - -function generateDisplayString(settings: OutputSettings) { - if ( - settings.js === JSFrameworkOption.REACT && - settings.css === CSSOption.TAILWIND - ) { - return ( -
- Generating React +{" "} - Tailwind code -
- ); - } else if ( - settings.js === JSFrameworkOption.NO_FRAMEWORK && - settings.css === CSSOption.TAILWIND - ) { - return ( -
- Generating HTML +{" "} - Tailwind code -
- ); - } else if ( - settings.js === JSFrameworkOption.NO_FRAMEWORK && - settings.css === CSSOption.BOOTSTRAP - ) { - return ( -
- Generating HTML +{" "} - Bootstrap code -
- ); + // TODO: Should never reach this out. Error out + return config; } } interface Props { - outputSettings: OutputSettings; - setOutputSettings: React.Dispatch>; + generatedCodeConfig: GeneratedCodeConfig; + setGeneratedCodeConfig: (config: GeneratedCodeConfig) => void; shouldDisableUpdates?: boolean; } function OutputSettingsSection({ - outputSettings, - setOutputSettings, + generatedCodeConfig, + setGeneratedCodeConfig, shouldDisableUpdates = false, }: 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 "No Framework" to use Bootstrap.' - ); - } - return { - css: CSSOption.TAILWIND, - js: JSFrameworkOption.REACT, - components: UIComponentOption.HTML, - }; - } else { - return { - ...prev, - css: convertStringToCSSOption(value), - }; - } - }); - }; - - const onJsFrameworkChange = (value: string) => { - if (value === JSFrameworkOption.REACT) { - setOutputSettings(() => ({ - css: CSSOption.TAILWIND, - js: value as JSFrameworkOption, - components: UIComponentOption.HTML, - })); - } else { - setOutputSettings((prev) => ({ - ...prev, - js: value as JSFrameworkOption, - })); - } - }; - - const onUIComponentOptionChange = (value: string) => { - if (value === UIComponentOption.IONIC) { - setOutputSettings(() => ({ - css: CSSOption.TAILWIND, - js: JSFrameworkOption.NO_FRAMEWORK, - components: value as UIComponentOption, - })); - } else { - setOutputSettings((prev) => ({ - ...prev, - components: value as UIComponentOption, - })); - } - }; - - const checkUIComponentOptionOrDefault = ( - valueItem: UIComponentOption - ): UIComponentOption => { - switch (valueItem) { - case UIComponentOption.IONIC: - if ( - outputSettings.js != JSFrameworkOption.NO_FRAMEWORK || - outputSettings.css != CSSOption.TAILWIND - ) { - return UIComponentOption.HTML; - } - } - return valueItem; - }; - - const checkCSSOptionOrDefault = (valueItem: CSSOption): CSSOption => { - switch (valueItem) { - default: - return valueItem; - } - }; - - const checkJSFrameworkOptionOrDefault = ( - valueItem: JSFrameworkOption - ): JSFrameworkOption => { - switch (valueItem) { - case JSFrameworkOption.REACT: - if (outputSettings.css != CSSOption.TAILWIND) { - return JSFrameworkOption.NO_FRAMEWORK; - } - break; - } - return valueItem; - }; - - useEffect(() => { - checkOutputSettingsOptions(); - }, [outputSettings]); - - const checkOutputSettingsOptions = () => { - if ( - isHiddenOption(outputSettings.css) || - isHiddenOption(outputSettings.js) || - isHiddenOption(outputSettings.components) - ) { - setOutputSettings((prev) => { - return { - css: checkCSSOptionOrDefault(prev.css), - js: checkJSFrameworkOptionOrDefault(prev.js), - components: checkUIComponentOptionOrDefault(prev.components), - }; - }); - } - }; - - const isHiddenOption = ( - option: CSSOption | JSFrameworkOption | UIComponentOption - ): boolean => { - if (Object.values(CSSOption).includes(option as CSSOption)) { - return checkCSSOptionOrDefault(option as CSSOption) != option; - } - if ( - Object.values(JSFrameworkOption).includes(option as JSFrameworkOption) - ) { - return ( - checkJSFrameworkOptionOrDefault(option as JSFrameworkOption) != option - ); - } - if ( - Object.values(UIComponentOption).includes(option as UIComponentOption) - ) { - return ( - checkUIComponentOptionOrDefault(option as UIComponentOption) != option - ); - } - return true; - }; - return (
- {generateDisplayString(outputSettings)}{" "} - {!shouldDisableUpdates && ( - - - - - -
-
-

Code Settings

-

- Customize your code output -

-
-
-
- - -
-
- - -
-
- - -
-
-
-
-
- )} +
+ Generating: + +
); } diff --git a/frontend/src/types.ts b/frontend/src/types.ts index 2fe2aec..deb370a 100644 --- a/frontend/src/types.ts +++ b/frontend/src/types.ts @@ -3,26 +3,12 @@ export enum EditorTheme { COBALT = "cobalt", } -export enum CSSOption { - TAILWIND = "tailwind", +// Keep in sync with backend (prompts.py) +export enum GeneratedCodeConfig { + HTML_TAILWIND = "html_tailwind", + REACT_TAILWIND = "react_tailwind", BOOTSTRAP = "bootstrap", -} - -export enum JSFrameworkOption { - NO_FRAMEWORK = "vanilla", - REACT = "react", - VUE = "vue", -} - -export enum UIComponentOption { - HTML = 'HTML', - IONIC = 'ionic' -} - -export interface OutputSettings { - css: CSSOption; - js: JSFrameworkOption; - components: UIComponentOption; + IONIC_TAILWIND = "ionic_tailwind", } export interface Settings { @@ -31,8 +17,10 @@ export interface Settings { screenshotOneApiKey: string | null; isImageGenerationEnabled: boolean; editorTheme: EditorTheme; - isTermOfServiceAccepted: boolean; // Only relevant for hosted version - accessCode: string | null; // Only relevant for hosted version + generatedCodeConfig: GeneratedCodeConfig; + // Only relevant for hosted version + isTermOfServiceAccepted: boolean; + accessCode: string | null; } export enum AppState { From 414d83c458b4ca8cbd9d973e65bf2dc362dd6d3d Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 30 Nov 2023 16:04:03 -0500 Subject: [PATCH 5/6] catch prompt assembly error --- backend/main.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/backend/main.py b/backend/main.py index eac04b0..108c4dc 100644 --- a/backend/main.py +++ b/backend/main.py @@ -137,12 +137,23 @@ async def stream_code(websocket: WebSocket): async def process_chunk(content): await websocket.send_json({"type": "chunk", "value": content}) - if params.get("resultImage") and params["resultImage"]: - prompt_messages = assemble_prompt( - params["image"], generated_code_config, params["resultImage"] + # 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", + } ) - else: - prompt_messages = assemble_prompt(params["image"], generated_code_config) + await websocket.close() + return # Image cache for updates so that we don't have to regenerate images image_cache = {} From dc28bd5e6b0b8377f0ae39ba1f43cda66b8c1a72 Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 30 Nov 2023 16:07:08 -0500 Subject: [PATCH 6/6] fix and add unit test --- backend/test_prompts.py | 49 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/backend/test_prompts.py b/backend/test_prompts.py index 2eaaaf4..5d8cd88 100644 --- a/backend/test_prompts.py +++ b/backend/test_prompts.py @@ -79,19 +79,58 @@ Return only the full code in 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 "" 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 Ionic so that it can run on a standalone page: + + + +- Use this script to include Tailwind: +- You can use Google Fonts +- ionicons for icons, add the following + + + +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" + "image_data_url", "html_tailwind", "result_image_data_url" ) assert tailwind_prompt[0]["content"] == TAILWIND_SYSTEM_PROMPT + react_tailwind_prompt = assemble_prompt( + "image_data_url", "react_tailwind", "result_image_data_url" + ) + assert react_tailwind_prompt[0]["content"] == REACT_TAILWIND_SYSTEM_PROMPT + bootstrap_prompt = assemble_prompt( - "image_data_url", {"css": "bootstrap", "js": "vanilla"}, "result_image_data_url" + "image_data_url", "bootstrap", "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" + ionic_tailwind = assemble_prompt( + "image_data_url", "ionic_tailwind", "result_image_data_url" ) - assert react_tailwind_prompt[0]["content"] == REACT_TAILWIND_SYSTEM_PROMPT + assert ionic_tailwind[0]["content"] == IONIC_TAILWIND_SYSTEM_PROMPT