Compare commits
2 Commits
main
...
generate-f
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2ec4bf59d3 | ||
|
|
d13ae72c06 |
@ -4,4 +4,5 @@ from typing import Literal
|
||||
InputMode = Literal[
|
||||
"image",
|
||||
"video",
|
||||
"text",
|
||||
]
|
||||
|
||||
@ -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,
|
||||
},
|
||||
]
|
||||
|
||||
37
backend/prompts/test_text_prompts.py
Normal file
37
backend/prompts/test_text_prompts.py
Normal file
@ -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 "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" 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: <script src="https://cdn.tailwindcss.com"></script>
|
||||
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
|
||||
Return only the full code in <html></html> 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()
|
||||
126
backend/prompts/text_prompts.py
Normal file
126
backend/prompts/text_prompts.py
Normal file
@ -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 "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" 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: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>"""
|
||||
|
||||
FORMAT_INSTRUCTIONS = """
|
||||
Return only the full code in <html></html> 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: <script src="https://cdn.tailwindcss.com"></script>
|
||||
{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:
|
||||
<script src="https://unpkg.com/react/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.js"></script>
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
{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: <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
{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:
|
||||
<script type="module" src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js"></script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"></script>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css" />
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- ionicons for icons, add the following <script > tags near the end of the page, right before the closing </body> tag:
|
||||
<script type="module">
|
||||
import ionicons from 'https://cdn.jsdelivr.net/npm/ionicons/+esm'
|
||||
</script>
|
||||
<script nomodule src="https://cdn.jsdelivr.net/npm/ionicons/dist/esm/ionicons.min.js"></script>
|
||||
<link href="https://cdn.jsdelivr.net/npm/ionicons/dist/collection/components/icon/icon.min.css" rel="stylesheet">
|
||||
|
||||
{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:
|
||||
<script src="https://registry.npmmirror.com/vue/3.3.11/files/dist/vue.global.js"></script>
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
{LIBRARY_INSTRUCTIONS}
|
||||
|
||||
{FORMAT_INSTRUCTIONS}
|
||||
"""
|
||||
|
||||
SVG_SYSTEM_PROMPT = f"""
|
||||
You are an expert at building SVGs.
|
||||
|
||||
{GENERAL_INSTRUCTIONS}
|
||||
|
||||
Return only the full code in <svg></svg> 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,
|
||||
)
|
||||
@ -16,7 +16,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 prompts.claude_prompts import VIDEO_PROMPT
|
||||
@ -185,12 +185,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(
|
||||
{
|
||||
@ -201,8 +207,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:
|
||||
|
||||
@ -42,6 +42,7 @@ import useBrowserTabIndicator from "./hooks/useBrowserTabIndicator";
|
||||
import TipLink from "./components/core/TipLink";
|
||||
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;
|
||||
|
||||
@ -49,7 +50,11 @@ function App() {
|
||||
const [appState, setAppState] = useState<AppState>(AppState.INITIAL);
|
||||
const [generatedCode, setGeneratedCode] = useState<string>("");
|
||||
|
||||
const [inputMode, setInputMode] = useState<"image" | "video">("image");
|
||||
const [inputMode, setInputMode] = useState<"image" | "video" | "text">(
|
||||
"image"
|
||||
);
|
||||
|
||||
const [initialPrompt, setInitialPrompt] = useState<string>("");
|
||||
|
||||
const [referenceImages, setReferenceImages] = useState<string[]>([]);
|
||||
const [executionConsole, setExecutionConsole] = useState<string[]>([]);
|
||||
@ -156,6 +161,7 @@ function App() {
|
||||
setAppState(AppState.INITIAL);
|
||||
setGeneratedCode("");
|
||||
setReferenceImages([]);
|
||||
setInitialPrompt("");
|
||||
setExecutionConsole([]);
|
||||
setUpdateInstruction("");
|
||||
setIsImportedFromCode(false);
|
||||
@ -181,7 +187,12 @@ function App() {
|
||||
}
|
||||
|
||||
// Re-run the create
|
||||
doCreate(referenceImages, inputMode);
|
||||
if (inputMode === "image" || inputMode === "video") {
|
||||
doCreate(referenceImages, inputMode);
|
||||
} else {
|
||||
// TODO: Fix this
|
||||
doCreateFromText(initialPrompt);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelCodeGeneration = () => {
|
||||
@ -225,14 +236,25 @@ function App() {
|
||||
(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) => {
|
||||
@ -294,6 +316,22 @@ function App() {
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
@ -351,7 +389,7 @@ function App() {
|
||||
{
|
||||
generationType: "update",
|
||||
inputMode,
|
||||
image: referenceImages[0],
|
||||
image: inputMode === "text" ? initialPrompt : referenceImages[0],
|
||||
history: updatedHistory,
|
||||
isImportedFromCode,
|
||||
},
|
||||
@ -462,6 +500,10 @@ function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{appState === AppState.INITIAL && (
|
||||
<GenerateFromText doCreateFromText={doCreateFromText} />
|
||||
)}
|
||||
|
||||
{(appState === AppState.CODING ||
|
||||
appState === AppState.CODE_READY) && (
|
||||
<>
|
||||
|
||||
@ -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<HTMLTextAreaElement>(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 (
|
||||
<div className="mt-4">
|
||||
{!isOpen ? (
|
||||
<div className="flex justify-center">
|
||||
<Button variant="secondary" onClick={() => setIsOpen(true)}>
|
||||
Generate from text prompt [BETA]
|
||||
</Button>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<Textarea
|
||||
ref={textareaRef}
|
||||
rows={2}
|
||||
placeholder="A Saas admin dashboard"
|
||||
className="w-full mb-4"
|
||||
value={text}
|
||||
onChange={(e) => setText(e.target.value)}
|
||||
/>
|
||||
<div className="flex justify-end">
|
||||
<Button onClick={handleGenerate}>Generate</Button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default GenerateFromText;
|
||||
@ -8,7 +8,7 @@ type CommonHistoryItem = {
|
||||
export type HistoryItem =
|
||||
| ({
|
||||
type: "ai_create";
|
||||
inputs: AiCreateInputs;
|
||||
inputs: AiCreateInputs | AiCreateInputsText;
|
||||
} & CommonHistoryItem)
|
||||
| ({
|
||||
type: "ai_edit";
|
||||
@ -23,6 +23,10 @@ export type AiCreateInputs = {
|
||||
image_url: string;
|
||||
};
|
||||
|
||||
export type AiCreateInputsText = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type AiEditInputs = {
|
||||
prompt: string;
|
||||
};
|
||||
|
||||
@ -33,7 +33,7 @@ export enum ScreenRecorderState {
|
||||
|
||||
export interface CodeGenerationParams {
|
||||
generationType: "create" | "update";
|
||||
inputMode: "image" | "video";
|
||||
inputMode: "image" | "video" | "text";
|
||||
image: string;
|
||||
resultImage?: string;
|
||||
history?: string[];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user