From 397fa0630f432c0b3802a5cdfdbafe822d5c1cbd Mon Sep 17 00:00:00 2001 From: Abi Raja Date: Tue, 28 Nov 2023 09:47:57 -0500 Subject: [PATCH] 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 ( -