diff --git a/backend/prompts/__init__.py b/backend/prompts/__init__.py index 4f2e3295d..ea863ee65 100644 --- a/backend/prompts/__init__.py +++ b/backend/prompts/__init__.py @@ -15,17 +15,47 @@ Generate code for a SVG that looks exactly like this. """ +TAILWIND_USER_PROMPT = """ +Incorporate the below given Tailwind CSS configuration into your project to customize its appearance. The configuration may have the custom fonts, colours, sizes, spacing which can be used to match the original image. Also add the configuration in the following way: + + +The configuration is: +""" + def assemble_imported_code_prompt( - code: str, stack: Stack, result_image_data_url: Union[str, None] = None + code: str, + stack: Stack, + tailwind_config: Union[str, None], + result_image_data_url: Union[str, None] = None, ) -> List[ChatCompletionMessageParam]: + system_content = IMPORTED_CODE_SYSTEM_PROMPTS[stack] - user_content = ( - "Here is the code of the app: " + code - if stack != "svg" - else "Here is the code of the SVG: " + code - ) + user_content: List[ChatCompletionContentPartParam] = [ + { + "type": "text", + "text": ( + "Here is the code of the app: " + code + if stack != "svg" + else "Here is the code of the SVG: " + code + ), + }, + ] + + if tailwind_config is not None: + user_content.insert( + 1, + { + "type": "text", + "text": TAILWIND_USER_PROMPT + tailwind_config, + }, + ) + return [ { "role": "system", @@ -42,8 +72,10 @@ def assemble_imported_code_prompt( def assemble_prompt( image_data_url: str, stack: Stack, + tailwind_config: Union[str, None], result_image_data_url: Union[str, None] = None, ) -> List[ChatCompletionMessageParam]: + system_content = SYSTEM_PROMPTS[stack] user_prompt = USER_PROMPT if stack != "svg" else SVG_USER_PROMPT @@ -67,6 +99,16 @@ def assemble_prompt( "image_url": {"url": result_image_data_url, "detail": "high"}, }, ) + + if tailwind_config is not None: + user_content.insert( + 2, + { + "type": "text", + "text": TAILWIND_USER_PROMPT + tailwind_config, + }, + ) + return [ { "role": "system", diff --git a/backend/routes/generate_code.py b/backend/routes/generate_code.py index 379042efe..254d58421 100644 --- a/backend/routes/generate_code.py +++ b/backend/routes/generate_code.py @@ -166,7 +166,7 @@ async def process_chunk(content: str): if params.get("isImportedFromCode") and params["isImportedFromCode"]: original_imported_code = params["history"][0] prompt_messages = assemble_imported_code_prompt( - original_imported_code, valid_stack + original_imported_code, valid_stack, params["tailwindConfig"] ) for index, text in enumerate(params["history"][1:]): if index % 2 == 0: @@ -185,10 +185,10 @@ async def process_chunk(content: str): try: if params.get("resultImage") and params["resultImage"]: prompt_messages = assemble_prompt( - params["image"], valid_stack, params["resultImage"] + params["image"], valid_stack, params["tailwindConfig"], params["resultImage"], ) else: - prompt_messages = assemble_prompt(params["image"], valid_stack) + prompt_messages = assemble_prompt(params["image"], valid_stack, params["tailwindConfig"]) except: await websocket.send_json( { diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 5156a0f0b..5c434665b 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useRef, useState, ChangeEvent } from "react"; import ImageUpload from "./components/ImageUpload"; import CodePreview from "./components/CodePreview"; import Preview from "./components/Preview"; @@ -14,7 +14,9 @@ import { } from "react-icons/fa"; import { Switch } from "./components/ui/switch"; +// import { Label } from "./components/ui/label"; import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; import { Textarea } from "@/components/ui/textarea"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs"; import SettingsDialog from "./components/SettingsDialog"; @@ -49,6 +51,8 @@ function App() { const [appState, setAppState] = useState(AppState.INITIAL); const [generatedCode, setGeneratedCode] = useState(""); + const [enableCustomTailwindConfig, setEnableCustomTailwindConfig] = useState(false); + const [inputMode, setInputMode] = useState<"image" | "video">("image"); const [referenceImages, setReferenceImages] = useState([]); @@ -71,6 +75,7 @@ function App() { codeGenerationModel: CodeGenerationModel.GPT_4O_2024_05_13, // Only relevant for hosted version isTermOfServiceAccepted: false, + tailwindConfig: null, }, "setting" ); @@ -393,6 +398,44 @@ function App() { setAppState(AppState.CODE_READY); } + const handleFileChange = (event: ChangeEvent) => { + const target = event.target as HTMLInputElement; + const file: File = (target.files as FileList)[0]; + if (file) { + const reader = new FileReader(); + reader.onload = (e) => { + const content = e!.target!.result; + if (typeof content === 'string') { + setSettings((s: Settings) => ({ + ...s, + tailwindConfig: content, + })); + } else { + toast.error('Please select a valid Tailwind config file'); + } + }; + reader.readAsText(file); + } + }; + + const handleRemoveFile = () => { + try { + const input = document.getElementById('config-file') as HTMLInputElement; + const dt = new DataTransfer(); + if(input == null) { + return; + } + input.files = dt.files + setSettings((s: Settings) => ({ + ...s, + tailwindConfig: null, + })); + } catch (err) { + toast.error('Please select a valid Tailwind config file'); + } + }; + + return (
{IS_RUNNING_ON_CLOUD && } @@ -403,7 +446,7 @@ function App() { /> )}
-
+

Screenshot to Code

@@ -425,16 +468,39 @@ function App() { } /> +
+ Enable custom Tailwind configuration: + + setEnableCustomTailwindConfig(!enableCustomTailwindConfig) + } + /> +
+ + {enableCustomTailwindConfig && (
+ + +
)} + {showReactWarning && ( -
+
Sorry - React is not currently working with GPT-4 Turbo. Please use GPT-4 Vision or Claude Sonnet. We are working on a fix.
)} {showGpt4OMessage && ( -
-

+

+

Now supporting GPT-4o. Higher quality and 2x faster. Give it a try!

@@ -446,7 +512,7 @@ function App() { {IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && } {IS_OPENAI_DOWN && ( -
+
OpenAI API is currently down. Try back in 30 minutes or later. We apologize for the inconvenience.
@@ -461,8 +527,7 @@ function App() { {/* Speed disclaimer for video mode */} {inputMode === "video" && (
Code generation from videos can take 3-4 minutes. We do multiple passes to get the best result. Please be patient. @@ -495,8 +560,8 @@ function App() { onChange={(e) => setUpdateInstruction(e.target.value)} value={updateInstruction} /> -
-
+
+
Include screenshot of current version?
-
+
-
+
)} {/* Reference image display */} -
+
{referenceImages.length > 0 && (
)}
-
+
{inputMode === "video" ? "Original Video" : "Original Screenshot"}
)} -
-

+
+

Console

{executionConsole.map((line, index) => (
{line}
@@ -600,7 +665,7 @@ function App() {
{appState === AppState.INITIAL && ( -
+
-
+
{appState === AppState.CODE_READY && ( <> @@ -627,7 +692,7 @@ function App() { diff --git a/frontend/src/components/SettingsDialog.tsx b/frontend/src/components/SettingsDialog.tsx index 97d8f3887..79ae01d6b 100644 --- a/frontend/src/components/SettingsDialog.tsx +++ b/frontend/src/components/SettingsDialog.tsx @@ -30,7 +30,7 @@ interface Props { function SettingsDialog({ settings, setSettings }: Props) { const handleThemeChange = (theme: EditorTheme) => { - setSettings((s) => ({ + setSettings((s: Settings) => ({ ...s, editorTheme: theme, })); @@ -49,7 +49,7 @@ function SettingsDialog({ settings, setSettings }: Props) {