8000 initial implementation of importing from code · ag-python/screenshot-to-code@52fee9e · GitHub
[go: up one dir, main page]

Skip to content

Commit 52fee9e

Browse files
committed
initial implementation of importing from code
1 parent 435402b commit 52fee9e

File tree

9 files changed

+196
-45
lines changed

9 files changed

+196
-45
lines changed

backend/imported_code_prompts.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT = """
2+
You are an expert Tailwind developer.
3+
4+
- 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.
5+
- Repeat elements as needed. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" or bad things will happen.
6+
- 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.
7+
8+
In terms of libraries,
9+
10+
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
11+
- You can use Google Fonts
12+
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
13+
14+
Return only the full code in <html></html> tags.
15+
Do not include markdown "```" or "```html" at the start or end.
16+
"""

backend/prompts.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from openai.types.chat import ChatCompletionMessageParam, ChatCompletionContentPartParam
44

5+
from imported_code_prompts import IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT
6+
57

68
TAILWIND_SYSTEM_PROMPT = """
79
You are an expert Tailwind developer
@@ -121,6 +123,23 @@
121123
"""
122124

123125

126+
def assemble_imported_code_prompt(
127+
code: str, result_image_data_url: Union[str, None] = None
128+
) -> List[ChatCompletionMessageParam]:
129+
system_content = IMPORTED_CODE_TAILWIND_SYSTEM_PROMPT
130+
return [
131+
{
132+
"role": "system",
133+
"content": system_content,
134+
},
135+
{
136+
"role": "user",
137+
"content": "Here is the code of the app: " + code,
138+
},
139+
]
140+
# TODO: Use result_image_data_url
141+
142+
124143
def assemble_prompt(
125144
image_data_url: str,
126145
generated_code_config: str,

backend/routes/generate_code.py

Lines changed: 48 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@
88
from mock_llm import mock_completion
99
from typing import Dict, List
1010
from image_generation import create_alt_url_mapping, generate_images
11-
from prompts import assemble_prompt
11+
from prompts import assemble_imported_code_prompt, assemble_prompt
1212
from access_token import validate_access_token
1313
from datetime import datetime
1414
import json
1515

16+
from utils import pprint_prompt # type: ignore
17+
1618

1719
router = APIRouter()
1820

@@ -122,43 +124,63 @@ async def throw_error(
122124
async def process_chunk(content: str):
123125
await websocket.send_json({"type": "chunk", "value": content})
124126

125-
# Assemble the prompt
126-
try:
127-
if params.get("resultImage") and params["resultImage"]:
128-
prompt_messages = assemble_prompt(
129-
params["image"], generated_code_config, params["resultImage"]
130-
)
131-
else:
132-
prompt_messages = assemble_prompt(params["image"], generated_code_config)
133-
except:
134-
await websocket.send_json(
135-
{
136-
"type": "error",
137-
"value": "Error assembling prompt. Contact support at support@picoapps.xyz",
138-
}
139-
)
140-
await websocket.close()
141-
return
142-
143127
# Image cache for updates so that we don't have to regenerate images
144128
image_cache: Dict[str, str] = {}
145129

146-
if params["generationType"] == "update":
147-
# Transform into message format
148-
# TODO: Move this to frontend
149-
for index, text in enumerate(params["history"]):
130+
# If this generation started off with imported code, we need to assemble the prompt differently
131+
if params.get("isImportedFromCode") and params["isImportedFromCode"]:
132+
original_imported_code = params["history"][0]
133+
prompt_messages = assemble_imported_code_prompt(original_imported_code)
134+
for index, text in enumerate(params["history"][1:]):
150135
if index % 2 == 0:
151136
message: ChatCompletionMessageParam = {
152-
"role": "assistant",
137+
"role": "user",
153138
"content": text,
154139
}
155140
else:
156141
message: ChatCompletionMessageParam = {
157-
"role": "user",
142+
"role": "assistant",
158143
"content": text,
159144
}
160145
prompt_messages.append(message)
161-
image_cache = create_alt_url_mapping(params["history"][-2])
146+
else:
147+
# Assemble the prompt
148+
try:
149+
if params.get("resultImage") and params["resultImage"]:
150+
prompt_messages = assemble_prompt(
151+
params["image"], generated_code_config, params["resultImage"]
152+
)
153+
else:
154+
prompt_messages = assemble_prompt(
155+
params["image"], generated_code_config
156+
)
157+
except:
158+
await websocket.send_json(
159+
{
160+
"type": "error",
161+
"value": "Error assembling prompt. Contact support at support@picoapps.xyz",
162+
}
163+
)
164+
await websocket.close()
165+
return
166+
167+
if params["generationType"] == "update":
168+
# Transform the history tree into message format
169+
# TODO: Move this to frontend
170+
for index, text in enumerate(params["history"]):
171+
if index % 2 == 0:
172+
message: ChatCompletionMessageParam = {
173+
"role": "assistant",
174+
"content": text,
175+
}
176+
else:
177+
message: ChatCompletionMessageParam = {
178+
"role": "user",
179+
"content": text,
180+
}
181+
prompt_messages.append(message)
182+
183+
image_cache = create_alt_url_mapping(params["history"][-2])
162184

163185
if SHOULD_MOCK_AI_RESPONSE:
164186
completion = await mock_completion(process_chunk)

frontend/src/App.tsx

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { History } from "./components/history/history_types";
3333
import HistoryDisplay from "./components/history/HistoryDisplay";
3434
import { extractHistoryTree } from "./components/history/utils";
3535
import toast from "react-hot-toast";
36+
import ImportCodeSection from "./components/ImportCodeSection";
3637

3738
const IS_OPENAI_DOWN = false;
3839

@@ -43,6 +44,7 @@ function App() {
4344
const [referenceImages, setReferenceImages] = useState<string[]>([]);
4445
const [executionConsole, setExecutionConsole] = useState<string[]>([]);
4546
const [updateInstruction, setUpdateInstruction] = useState("");
47+
const [isImportedFromCode, setIsImportedFromCode] = useState<boolean>(false);
4648

4749
// Settings
4850
const [settings, setSettings] = usePersistedState<Settings>(
@@ -118,6 +120,8 @@ function App() {
118120
setReferenceImages([]);
119121
setExecutionConsole([]);
120122
setAppHistory([]);
123+
setCurrentVersion(null);
124+
setIsImportedFromCode(false);
121125
};
122126

123127
const stop = () => {
@@ -231,6 +235,7 @@ function App() {
231235
image: referenceImages[0],
232236
resultImage: resultImage,
233237
history: updatedHistory,
238+
isImportedFromCode,
234239
},
235240
currentVersion
236241
);
@@ -240,6 +245,7 @@ function App() {
240245
generationType: "update",
241246
image: referenceImages[0],
242247
history: updatedHistory,
248+
isImportedFromCode,
243249
},
244250
currentVersion
245251
);
@@ -256,6 +262,21 @@ function App() {
256262
}));
257263
};
258264

265+
function importFromCode(code: string) {
266+
setAppState(AppState.CODE_READY);
267+
setGeneratedCode(code);
268+
setAppHistory([
269+
{
270+
type: "code_create",
271+
parentIndex: null,
272+
code,
273+
inputs: { code },
274+
},
275+
]);
276+
setCurrentVersion(0);
277+
setIsImportedFromCode(true);
278+
}
279+
259280
return (
260281
<div className="mt-2 dark:bg-black dark:text-white">
261282
{IS_RUNNING_ON_CLOUD && <PicoBadge settings={settings} />}
@@ -364,22 +385,24 @@ function App() {
364385

365386
{/* Reference image display */}
366387
<div className="flex gap-x-2 mt-2">
367-
<div className="flex flex-col">
368-
<div
369-
className={classNames({
370-
"scanning relative": appState === AppState.CODING,
371-
})}
372-
>
373-
<img
374-
className="w-[340px] border border-gray-200 rounded-md"
375-
src={referenceImages[0]}
376-
alt="Reference"
377-
/>
378-
</div>
379-
<div className="text-gray-400 uppercase text-sm text-center mt-1">
380-
Original Screenshot
388+
{referenceImages.length > 0 && (
389+
<div className="flex flex-col">
390+
<div
391+
className={classNames({
392+
"scanning relative": appState === AppState.CODING,
393+
})}
394+
>
395+
<img
396+
className="w-[340px] border border-gray-200 rounded-md"
397+
src={referenceImages[0]}
398+
alt="Reference"
399+
/>
400+
</div>
401+
<div className="text-gray-400 uppercase text-sm text-center mt-1">
402+
Original Screenshot
403+
</div>
381404
</div>
382-
</div>
405+
)}
383406
<div className="bg-gray-400 px-4 py-2 rounded text-sm hidden">
384407
<h2 className="text-lg mb-4 border-b border-gray-800">
385408
Console
@@ -424,6 +447,7 @@ function App() {
424447
doCreate={doCreate}
425448
screenshotOneApiKey={settings.screenshotOneApiKey}
426449
/>
450+
<ImportCodeSection importFromCode={importFromCode} />
427451
</div>
428452
)}
429453

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { useState } from "react";
2+
import { Button } from "./ui/button";
3+
import {
4+
Dialog,
5+
DialogContent,
6+
DialogDescription,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogTitle,
10+
DialogTrigger,
11+
} from "./ui/dialog";
12+
import { Textarea } from "./ui/textarea";
13+
14+
interface Props {
15+
importFromCode: (code: string) => void;
16+
}
17+
18+
function ImportCodeSection({ importFromCode }: Props) {
19+
const [code, setCode] = useState("");
20+
return (
21+
<Dialog>
22+
<DialogTrigger asChild>
23+
<Button variant="secondary">Import from Code</Button>
24+
</DialogTrigger>
25+
<DialogContent className="sm:max-w-[425px]">
26+
<DialogHeader>
27+
<DialogTitle>Paste in your HTML code</DialogTitle>
28+
<DialogDescription>
29+
Make sure that the code you're importing is valid HTML.
30+
</DialogDescription>
31+
</DialogHeader>
32+
33+
<Textarea
34+
value={code}
35+
onChange={(e) => setCode(e.target.value)}
36+
className="w-full h-64"
37+
/>
38+
39+
<DialogFooter>
40+
<Button type="submit" onClick={() => importFromCode(code)}>
41+
Import
42+
</Button>
43+
</DialogFooter>
44+
</DialogContent>
45+
</Dialog>
46+
);
47+
}
48+
49+
export default ImportCodeSection;

frontend/src/components/history/HistoryDisplay.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ function displayHistoryItemType(itemType: HistoryItemType) {
2121
return "Create";
2222
case "ai_edit":
2323
return "Edit";
24+
case "code_create":
25+
return "Imported from code";
2426
default: {
2527
const exhaustiveCheck: never = itemType;
2628
throw new Error(`Unhandled case: ${exhaustiveCheck}`);
@@ -62,7 +64,11 @@ export default function HistoryDisplay({
6264
{" "}
6365
<div className="flex gap-x-1 truncate">
6466
<h2 className="text-sm truncate">
65-
{item.type === "ai_edit" ? item.inputs.prompt : "Create"}
67+
{item.type === "ai_edit"
68+
? item.inputs.prompt
69+
: item.type === "ai_create"
70+
? "Create"
71+
: "Imported from code"}
6672
</h2>
6773
{/* <h2 className="text-sm">{displayHistoryItemType(item.type)}</h2> */}
6874
{item.parentIndex !== null &&
@@ -76,7 +82,11 @@ export default function HistoryDisplay({
7682
</HoverCardTrigger>
7783
<HoverCardContent>
7884
<div>
79-
{item.type === "ai_edit" ? item.inputs.prompt : "Create"}
85+
{item.type === "ai_edit"
86+
? item.inputs.prompt
87+
: item.type === "ai_create"
88+
? "Create"
89+
: "Imported from code"}
8090
</div>
8191
<Badge>{displayHistoryItemType(item.type)}</Badge>
8292
</HoverCardContent>

frontend/src/components/history/history_types.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export type HistoryItemType = "ai_create" | "ai_edit";
1+
export type HistoryItemType = "ai_create" | "ai_edit" | "code_create";
22

33
type CommonHistoryItem = {
44
parentIndex: null | number;
@@ -13,6 +13,10 @@ export type HistoryItem =
1313
| ({
1414
type: "ai_edit";
1515
inputs: AiEditInputs;
16+
} & CommonHistoryItem)
17+
| ({
18+
type: "code_create";
19+
inputs: CodeCreateInputs;
1620
} & CommonHistoryItem);
1721

1822
export type AiCreateInputs = {
@@ -23,4 +27,8 @@ export type AiEditInputs = {
2327
prompt: string;
2428
};
2529

30+
export type CodeCreateInputs = {
31+
code: string;
32+
};
33+
2634
export type History = HistoryItem[];

frontend/src/components/history/utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@ export function extractHistoryTree(
1414
if (item.type === "ai_create") {
1515
// Don't include the image for ai_create
1616
flatHistory.unshift(item.code);
17-
} else {
17+
} else if (item.type === "ai_edit") {
1818
flatHistory.unshift(item.code);
1919
flatHistory.unshift(item.inputs.prompt);
20+
} else if (item.type === "code_create") {
21+
flatHistory.unshift(item.code);
2022
}
2123

2224
// Move to the parent of the current item

frontend/src/generateCode.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export interface CodeGenerationParams {
1212
image: string;
1313
resultImage?: string;
1414
history?: string[];
15+
isImportedFromCode?: boolean;
1516
// isImageGenerationEnabled: boolean; // TODO: Merge with Settings type in types.ts
1617
}
1718

0 commit comments

Comments
 (0)
0