diff --git a/backend/imported_code_prompts.py b/backend/imported_code_prompts.py new file mode 100644 index 0000000..28b5fa3 --- /dev/null +++ b/backend/imported_code_prompts.py @@ -0,0 +1,16 @@ +IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT = """ +You are an expert Tailwind developer. + +- 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. 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. +""" diff --git a/backend/prompts.py b/backend/prompts.py index f52c195..554f62f 100644 --- a/backend/prompts.py +++ b/backend/prompts.py @@ -2,6 +2,8 @@ from typing import List, Union from openai.types.chat import ChatCompletionMessageParam, ChatCompletionContentPartParam +from imported_code_prompts import IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT + TAILWIND_SYSTEM_PROMPT = """ You are an expert Tailwind developer @@ -121,6 +123,23 @@ Generate code for a web page that looks exactly like this. """ +def assemble_imported_code_prompt( + code: str, result_image_data_url: Union[str, None] = None +) -> List[ChatCompletionMessageParam]: + system_content = IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT + return [ + { + "role": "system", + "content": system_content, + }, + { + "role": "user", + "content": "Here is the code of the app: " + code, + }, + ] + # TODO: Use result_image_data_url + + def assemble_prompt( image_data_url: str, generated_code_config: str, diff --git a/backend/routes/generate_code.py b/backend/routes/generate_code.py index a44aeac..eae9b98 100644 --- a/backend/routes/generate_code.py +++ b/backend/routes/generate_code.py @@ -8,11 +8,13 @@ from openai.types.chat import ChatCompletionMessageParam from mock_llm import mock_completion from typing import Dict, List from image_generation import create_alt_url_mapping, generate_images -from prompts import assemble_prompt +from prompts import assemble_imported_code_prompt, assemble_prompt from access_token import validate_access_token from datetime import datetime import json +from utils import pprint_prompt # type: ignore + router = APIRouter() @@ -122,43 +124,63 @@ async def stream_code(websocket: WebSocket): async def process_chunk(content: str): await websocket.send_json({"type": "chunk", "value": content}) - # 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", - } - ) - await websocket.close() - return - # Image cache for updates so that we don't have to regenerate images image_cache: Dict[str, str] = {} - if params["generationType"] == "update": - # Transform into message format - # TODO: Move this to frontend - for index, text in enumerate(params["history"]): + # If this generation started off with imported code, we need to assemble the prompt differently + if params.get("isImportedFromCode") and params["isImportedFromCode"]: + original_imported_code = params["history"][0] + prompt_messages = assemble_imported_code_prompt(original_imported_code) + for index, text in enumerate(params["history"][1:]): if index % 2 == 0: - message: ChatCompletionMessageParam = { - "role": "assistant", - "content": text, - } - else: message: ChatCompletionMessageParam = { "role": "user", "content": text, } + else: + message: ChatCompletionMessageParam = { + "role": "assistant", + "content": text, + } prompt_messages.append(message) - image_cache = create_alt_url_mapping(params["history"][-2]) + else: + # 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", + } + ) + await websocket.close() + return + + if params["generationType"] == "update": + # Transform the history tree into message format + # TODO: Move this to frontend + for index, text in enumerate(params["history"]): + if index % 2 == 0: + message: ChatCompletionMessageParam = { + "role": "assistant", + "content": text, + } + else: + message: ChatCompletionMessageParam = { + "role": "user", + "content": text, + } + prompt_messages.append(message) + + image_cache = create_alt_url_mapping(params["history"][-2]) if SHOULD_MOCK_AI_RESPONSE: completion = await mock_completion(process_chunk) diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 95cd8d3..03ec3ba 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -33,6 +33,7 @@ import { History } from "./components/history/history_types"; import HistoryDisplay from "./components/history/HistoryDisplay"; import { extractHistoryTree } from "./components/history/utils"; import toast from "react-hot-toast"; +import ImportCodeSection from "./components/ImportCodeSection"; const IS_OPENAI_DOWN = false; @@ -43,6 +44,7 @@ function App() { const [referenceImages, setReferenceImages] = useState([]); const [executionConsole, setExecutionConsole] = useState([]); const [updateInstruction, setUpdateInstruction] = useState(""); + const [isImportedFromCode, setIsImportedFromCode] = useState(false); // Settings const [settings, setSettings] = usePersistedState( @@ -118,6 +120,8 @@ function App() { setReferenceImages([]); setExecutionConsole([]); setAppHistory([]); + setCurrentVersion(null); + setIsImportedFromCode(false); }; const stop = () => { @@ -231,6 +235,7 @@ function App() { image: referenceImages[0], resultImage: resultImage, history: updatedHistory, + isImportedFromCode, }, currentVersion ); @@ -240,6 +245,7 @@ function App() { generationType: "update", image: referenceImages[0], history: updatedHistory, + isImportedFromCode, }, currentVersion ); @@ -256,6 +262,21 @@ function App() { })); }; + function importFromCode(code: string) { + setAppState(AppState.CODE_READY); + setGeneratedCode(code); + setAppHistory([ + { + type: "code_create", + parentIndex: null, + code, + inputs: { code }, + }, + ]); + setCurrentVersion(0); + setIsImportedFromCode(true); + } + return (
{IS_RUNNING_ON_CLOUD && } @@ -364,22 +385,24 @@ function App() { {/* Reference image display */}
-
-
- Reference + {referenceImages.length > 0 && ( +
+
+ Reference +
+
+ Original Screenshot +
-
- Original Screenshot -
-
+ )}

Console @@ -424,6 +447,7 @@ function App() { doCreate={doCreate} screenshotOneApiKey={settings.screenshotOneApiKey} /> +

)} diff --git a/frontend/src/components/ImportCodeSection.tsx b/frontend/src/components/ImportCodeSection.tsx new file mode 100644 index 0000000..0199413 --- /dev/null +++ b/frontend/src/components/ImportCodeSection.tsx @@ -0,0 +1,49 @@ +import { useState } from "react"; +import { Button } from "./ui/button"; +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "./ui/dialog"; +import { Textarea } from "./ui/textarea"; + +interface Props { + importFromCode: (code: string) => void; +} + +function ImportCodeSection({ importFromCode }: Props) { + const [code, setCode] = useState(""); + return ( + + + + + + + Paste in your HTML code + + Make sure that the code you're importing is valid HTML. + + + +