From f51e70d701fbefb3b8dd71477cf15a1069749ced Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Thu, 30 Nov 2023 15:58:01 -0500 Subject: [PATCH] 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 {