diff --git a/backend/custom_types.py b/backend/custom_types.py
index b6c9fee..795eb18 100644
--- a/backend/custom_types.py
+++ b/backend/custom_types.py
@@ -4,4 +4,5 @@ from typing import Literal
InputMode = Literal[
"image",
"video",
+ "text",
]
diff --git a/backend/prompts/__init__.py b/backend/prompts/__init__.py
index dc96ab9..de544a7 100644
--- a/backend/prompts/__init__.py
+++ b/backend/prompts/__init__.py
@@ -5,6 +5,7 @@ from llm import Llm
from prompts.imported_code_prompts import IMPORTED_CODE_SYSTEM_PROMPTS
from prompts.screenshot_system_prompts import SYSTEM_PROMPTS
+from prompts.text_prompts import SYSTEM_PROMPTS as TEXT_SYSTEM_PROMPTS
from prompts.types import Stack
@@ -87,3 +88,22 @@ def assemble_prompt(
"content": user_content,
},
]
+
+
+def assemble_text_prompt(
+ text_prompt: str,
+ stack: Stack,
+) -> List[ChatCompletionMessageParam]:
+
+ system_content = TEXT_SYSTEM_PROMPTS[stack]
+
+ return [
+ {
+ "role": "system",
+ "content": system_content,
+ },
+ {
+ "role": "user",
+ "content": "Generate UI for " + text_prompt,
+ },
+ ]
diff --git a/backend/prompts/test_text_prompts.py b/backend/prompts/test_text_prompts.py
new file mode 100644
index 0000000..9d38fd7
--- /dev/null
+++ b/backend/prompts/test_text_prompts.py
@@ -0,0 +1,37 @@
+import unittest
+from prompts.text_prompts import HTML_TAILWIND_SYSTEM_PROMPT
+
+
+class TestTextPrompts(unittest.TestCase):
+ def test_html_tailwind_system_prompt(self):
+ self.maxDiff = None
+
+ print(HTML_TAILWIND_SYSTEM_PROMPT)
+
+ expected_prompt = """
+You are an expert Tailwind developer.
+
+
+- Make sure to make it look modern and sleek.
+- Use modern, professional fonts and colors.
+- Follow UX best practices.
+- Do not add comments in the code such as "" and "" in place of writing the full code. WRITE THE FULL CODE.
+- 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.
+Reply with only the code, and no text/explanation before and after the code.
+"""
+ self.assertEqual(HTML_TAILWIND_SYSTEM_PROMPT.strip(), expected_prompt.strip())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/backend/prompts/text_prompts.py b/backend/prompts/text_prompts.py
new file mode 100644
index 0000000..d6db94b
--- /dev/null
+++ b/backend/prompts/text_prompts.py
@@ -0,0 +1,126 @@
+from prompts.types import SystemPrompts
+
+GENERAL_INSTRUCTIONS = """
+- Make sure to make it look modern and sleek.
+- Use modern, professional fonts and colors.
+- Follow UX best practices.
+- Do not add comments in the code such as "" and "" in place of writing the full code. WRITE THE FULL CODE.
+- 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."""
+
+LIBRARY_INSTRUCTIONS = """
+- You can use Google Fonts
+- Font Awesome for icons: """
+
+FORMAT_INSTRUCTIONS = """
+Return only the full code in tags.
+Do not include markdown "```" or "```html" at the start or end.
+Reply with only the code, and no text/explanation before and after the code.
+"""
+
+HTML_TAILWIND_SYSTEM_PROMPT = f"""
+You are an expert Tailwind developer.
+
+{GENERAL_INSTRUCTIONS}
+
+In terms of libraries,
+
+- Use this script to include Tailwind:
+{LIBRARY_INSTRUCTIONS}
+
+{FORMAT_INSTRUCTIONS}
+"""
+
+HTML_CSS_SYSTEM_PROMPT = f"""
+You are an expert HTML, CSS and JS developer.
+
+{GENERAL_INSTRUCTIONS}
+
+In terms of libraries,
+{LIBRARY_INSTRUCTIONS}
+
+{FORMAT_INSTRUCTIONS}
+"""
+
+REACT_TAILWIND_SYSTEM_PROMPT = f"""
+You are an expert React/Tailwind developer.
+
+{GENERAL_INSTRUCTIONS}
+
+In terms of libraries,
+- Use these script to include React so that it can run on a standalone page:
+
+
+
+- Use this script to include Tailwind:
+{LIBRARY_INSTRUCTIONS}
+
+{FORMAT_INSTRUCTIONS}
+"""
+
+BOOTSTRAP_SYSTEM_PROMPT = f"""
+You are an expert Bootstrap, HTML and JS developer.
+
+{GENERAL_INSTRUCTIONS}
+
+In terms of libraries,
+- Use this script to include Bootstrap:
+{LIBRARY_INSTRUCTIONS}
+
+{FORMAT_INSTRUCTIONS}
+"""
+
+IONIC_TAILWIND_SYSTEM_PROMPT = f"""
+You are an expert Ionic/Tailwind developer.
+
+{GENERAL_INSTRUCTIONS}
+
+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
+
+
+
+{FORMAT_INSTRUCTIONS}
+"""
+
+VUE_TAILWIND_SYSTEM_PROMPT = f"""
+You are an expert Vue/Tailwind developer.
+
+{GENERAL_INSTRUCTIONS}
+
+In terms of libraries,
+- Use these script to include Vue so that it can run on a standalone page:
+
+- Use this script to include Tailwind:
+{LIBRARY_INSTRUCTIONS}
+
+{FORMAT_INSTRUCTIONS}
+"""
+
+SVG_SYSTEM_PROMPT = f"""
+You are an expert at building SVGs.
+
+{GENERAL_INSTRUCTIONS}
+
+Return only the full code in tags.
+Do not include markdown "```" or "```svg" at the start or end.
+"""
+
+
+SYSTEM_PROMPTS = SystemPrompts(
+ html_css=HTML_CSS_SYSTEM_PROMPT,
+ html_tailwind=HTML_TAILWIND_SYSTEM_PROMPT,
+ react_tailwind=REACT_TAILWIND_SYSTEM_PROMPT,
+ bootstrap=BOOTSTRAP_SYSTEM_PROMPT,
+ ionic_tailwind=IONIC_TAILWIND_SYSTEM_PROMPT,
+ vue_tailwind=VUE_TAILWIND_SYSTEM_PROMPT,
+ svg=SVG_SYSTEM_PROMPT,
+)
diff --git a/backend/routes/generate_code.py b/backend/routes/generate_code.py
index 269723a..f0ccfb8 100644
--- a/backend/routes/generate_code.py
+++ b/backend/routes/generate_code.py
@@ -17,7 +17,7 @@ from openai.types.chat import ChatCompletionMessageParam
from mock_llm import mock_completion
from typing import Dict, List, Union, cast, get_args
from image_generation import create_alt_url_mapping, generate_images
-from prompts import assemble_imported_code_prompt, assemble_prompt
+from prompts import assemble_imported_code_prompt, assemble_prompt, assemble_text_prompt
from datetime import datetime
import json
from routes.logging_utils import PaymentMethod, send_to_saas_backend
@@ -230,12 +230,18 @@ async def stream_code(websocket: WebSocket):
else:
# Assemble the prompt
try:
- if params.get("resultImage") and params["resultImage"]:
- prompt_messages = assemble_prompt(
- params["image"], valid_stack, params["resultImage"]
- )
+ if validated_input_mode == "image":
+ if params.get("resultImage") and params["resultImage"]:
+ prompt_messages = assemble_prompt(
+ params["image"], valid_stack, params["resultImage"]
+ )
+ else:
+ prompt_messages = assemble_prompt(params["image"], valid_stack)
+ elif validated_input_mode == "text":
+ prompt_messages = assemble_text_prompt(params["image"], valid_stack)
else:
- prompt_messages = assemble_prompt(params["image"], valid_stack)
+ await throw_error("Invalid input mode")
+ return
except:
await websocket.send_json(
{
@@ -246,8 +252,8 @@ async def stream_code(websocket: WebSocket):
await websocket.close()
return
+ # Transform the history tree into message format for updates
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:
diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx
index 7ace365..8186c31 100644
--- a/frontend/src/App.tsx
+++ b/frontend/src/App.tsx
@@ -49,6 +49,7 @@ import TipLink from "./components/core/TipLink";
import FeedbackCallNote from "./components/user-feedback/FeedbackCallNote";
import SelectAndEditModeToggleButton from "./components/select-and-edit/SelectAndEditModeToggleButton";
import { useAppStore } from "./store/app-store";
+import GenerateFromText from "./components/generate-from-text/GenerateFromText";
const IS_OPENAI_DOWN = false;
@@ -60,7 +61,11 @@ function App({ navbarComponent }: Props) {
const [appState, setAppState] = useState(AppState.INITIAL);
const [generatedCode, setGeneratedCode] = useState("");
- const [inputMode, setInputMode] = useState<"image" | "video">("image");
+ const [inputMode, setInputMode] = useState<"image" | "video" | "text">(
+ "image"
+ );
+
+ const [initialPrompt, setInitialPrompt] = useState("");
const [referenceImages, setReferenceImages] = useState([]);
const [executionConsole, setExecutionConsole] = useState([]);
@@ -177,6 +182,7 @@ function App({ navbarComponent }: Props) {
setAppState(AppState.INITIAL);
setGeneratedCode("");
setReferenceImages([]);
+ setInitialPrompt("");
setExecutionConsole([]);
setUpdateInstruction("");
setIsImportedFromCode(false);
@@ -207,7 +213,12 @@ function App({ navbarComponent }: Props) {
addEvent("Regenerate");
// Re-run the create
- doCreate(referenceImages, inputMode);
+ if (inputMode === "image" || inputMode === "video") {
+ doCreate(referenceImages, inputMode);
+ } else {
+ // TODO: Fix this
+ doCreateFromText(initialPrompt);
+ }
};
const cancelCodeGeneration = () => {
@@ -258,14 +269,25 @@ function App({ navbarComponent }: Props) {
(code) => {
setGeneratedCode(code);
if (params.generationType === "create") {
- setAppHistory([
- {
- type: "ai_create",
- parentIndex: null,
- code,
- inputs: { image_url: referenceImages[0] },
- },
- ]);
+ if (inputMode === "image" || inputMode === "video") {
+ setAppHistory([
+ {
+ type: "ai_create",
+ parentIndex: null,
+ code,
+ inputs: { image_url: referenceImages[0] },
+ },
+ ]);
+ } else {
+ setAppHistory([
+ {
+ type: "ai_create",
+ parentIndex: null,
+ code,
+ inputs: { text: params.image },
+ },
+ ]);
+ }
setCurrentVersion(0);
} else {
setAppHistory((prev) => {
@@ -332,6 +354,22 @@ function App({ navbarComponent }: Props) {
}
}
+ function doCreateFromText(text: string) {
+ // Reset any existing state
+ reset();
+
+ setInputMode("text");
+ setInitialPrompt(text);
+ doGenerateCode(
+ {
+ generationType: "create",
+ inputMode: "text",
+ image: text,
+ },
+ currentVersion
+ );
+ }
+
// Subsequent updates
async function doUpdate(
updateInstruction: string,
@@ -391,7 +429,7 @@ function App({ navbarComponent }: Props) {
{
generationType: "update",
inputMode,
- image: referenceImages[0],
+ image: inputMode === "text" ? initialPrompt : referenceImages[0],
history: updatedHistory,
isImportedFromCode,
},
@@ -509,6 +547,10 @@ function App({ navbarComponent }: Props) {
)}
+ {appState === AppState.INITIAL && (
+
+ )}
+
{(appState === AppState.CODING ||
appState === AppState.CODE_READY) && (
<>
diff --git a/frontend/src/components/generate-from-text/GenerateFromText.tsx b/frontend/src/components/generate-from-text/GenerateFromText.tsx
new file mode 100644
index 0000000..35919f9
--- /dev/null
+++ b/frontend/src/components/generate-from-text/GenerateFromText.tsx
@@ -0,0 +1,57 @@
+import { useState, useRef, useEffect } from "react";
+import { Button } from "../ui/button";
+import { Textarea } from "../ui/textarea";
+import toast from "react-hot-toast";
+
+interface GenerateFromTextProps {
+ doCreateFromText: (text: string) => void;
+}
+
+function GenerateFromText({ doCreateFromText }: GenerateFromTextProps) {
+ const [isOpen, setIsOpen] = useState(false);
+ const [text, setText] = useState("");
+ const textareaRef = useRef(null);
+
+ useEffect(() => {
+ if (isOpen && textareaRef.current) {
+ textareaRef.current.focus();
+ }
+ }, [isOpen]);
+
+ const handleGenerate = () => {
+ if (text.trim() === "") {
+ // Assuming there's a toast function available in the context
+ toast.error("Please enter a prompt to generate from");
+ return;
+ }
+ doCreateFromText(text);
+ };
+
+ return (
+