Merge branch 'main' into feat/add-image-comparison

This commit is contained in:
clean99 2023-11-22 10:14:22 +08:00 committed by GitHub
commit cc22292941
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 65 additions and 31 deletions

7
.gitignore vendored
View File

@ -1,6 +1,13 @@
.aider* .aider*
# Project-related files
# Run logs # Run logs
backend/run_logs/* backend/run_logs/*
# Weird Docker setup related files
backend/backend/*
# Env vars
frontend/.env.local
.env .env

View File

@ -1,6 +1,6 @@
# screenshot-to-code # screenshot-to-code
This simple app converts a screenshot to HTML/Tailwind CSS. It uses GPT-4 Vision to generate the code and DALL-E 3 to generate similar-looking images. This simple app converts a screenshot to HTML/Tailwind CSS. It uses GPT-4 Vision to generate the code and DALL-E 3 to generate similar-looking images. You can now also enter a URL to clone a live website!
https://github.com/abi/screenshot-to-code/assets/23818/6cebadae-2fe3-4986-ac6a-8fb9db030045 https://github.com/abi/screenshot-to-code/assets/23818/6cebadae-2fe3-4986-ac6a-8fb9db030045

View File

@ -59,7 +59,7 @@ def write_logs(prompt_messages, completion):
@app.websocket("/generate-code") @app.websocket("/generate-code")
async def stream_code_test(websocket: WebSocket): async def stream_code(websocket: WebSocket):
await websocket.accept() await websocket.accept()
params = await websocket.receive_json() params = await websocket.receive_json()

View File

@ -23,6 +23,7 @@
"clsx": "^2.0.0", "clsx": "^2.0.0",
"codemirror": "^6.0.1", "codemirror": "^6.0.1",
"html2canvas": "^1.4.1", "html2canvas": "^1.4.1",
"copy-to-clipboard": "^3.3.3",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-dropzone": "^14.2.3", "react-dropzone": "^14.2.3",

View File

@ -1,4 +1,4 @@
import { useRef, useState } from "react"; import { useRef, useState, useCallback } from "react";
import ImageUpload from "./components/ImageUpload"; import ImageUpload from "./components/ImageUpload";
import CodePreview from "./components/CodePreview"; import CodePreview from "./components/CodePreview";
import Preview from "./components/Preview"; import Preview from "./components/Preview";
@ -7,18 +7,22 @@ import Spinner from "./components/Spinner";
import classNames from "classnames"; import classNames from "classnames";
import { import {
FaCode, FaCode,
FaCopy,
FaDesktop, FaDesktop,
FaDownload, FaDownload,
FaMobile, FaMobile,
FaUndo, FaUndo,
} from "react-icons/fa"; } from "react-icons/fa";
import { Switch } from "./components/ui/switch"; import { Switch } from "./components/ui/switch";
import copy from "copy-to-clipboard";
import toast from "react-hot-toast";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs";
import CodeMirror from "./components/CodeMirror"; import CodeMirror from "./components/CodeMirror";
import SettingsDialog from "./components/SettingsDialog"; import SettingsDialog from "./components/SettingsDialog";
import { AppStatus, Settings, USER_CLOSE_WEB_SOCKET_CODE } from "@/types"; import { AppStatus, Settings } from "@/types";
import { IS_RUNNING_ON_CLOUD } from "./config"; import { IS_RUNNING_ON_CLOUD } from "./config";
import { PicoBadge } from "./components/PicoBadge"; import { PicoBadge } from "./components/PicoBadge";
import { OnboardingNote } from "./components/OnboardingNote"; import { OnboardingNote } from "./components/OnboardingNote";
@ -26,12 +30,10 @@ import { usePersistedState } from "./hooks/usePersistedState";
import { UrlInputSection } from "./components/UrlInputSection"; import { UrlInputSection } from "./components/UrlInputSection";
import TermsOfServiceDialog from "./components/TermsOfServiceDialog"; import TermsOfServiceDialog from "./components/TermsOfServiceDialog";
import html2canvas from 'html2canvas'; import html2canvas from 'html2canvas';
import { USER_CLOSE_WEB_SOCKET_CODE } from "./constants";
function App() { function App() {
const [appState, setAppState] = useState<AppStatus>( const [appState, setAppState] = useState<AppState>(AppState.INITIAL);
AppStatus.INITIAL
);
const [generatedCode, setGeneratedCode] = useState<string>(""); const [generatedCode, setGeneratedCode] = useState<string>("");
const [referenceImages, setReferenceImages] = useState<string[]>([]); const [referenceImages, setReferenceImages] = useState<string[]>([]);
const [executionConsole, setExecutionConsole] = useState<string[]>([]); const [executionConsole, setExecutionConsole] = useState<string[]>([]);
@ -78,7 +80,7 @@ function App() {
}; };
const reset = () => { const reset = () => {
setAppState(AppStatus.INITIAL); setAppState(AppState.INITIAL);
setGeneratedCode(""); setGeneratedCode("");
setReferenceImages([]); setReferenceImages([]);
setExecutionConsole([]); setExecutionConsole([]);
@ -93,7 +95,7 @@ function App() {
function doGenerateCode(params: CodeGenerationParams) { function doGenerateCode(params: CodeGenerationParams) {
setExecutionConsole([]); setExecutionConsole([]);
setAppState(AppStatus.CODING); setAppState(AppState.CODING);
// Merge settings with params // Merge settings with params
const updatedParams = { ...params, ...settings }; const updatedParams = { ...params, ...settings };
@ -104,7 +106,7 @@ function App() {
(token) => setGeneratedCode((prev) => prev + token), (token) => setGeneratedCode((prev) => prev + token),
(code) => setGeneratedCode(code), (code) => setGeneratedCode(code),
(line) => setExecutionConsole((prev) => [...prev, line]), (line) => setExecutionConsole((prev) => [...prev, line]),
() => setAppState(AppStatus.CODE_READY) () => setAppState(AppState.CODE_READY)
); );
} }
@ -143,6 +145,11 @@ function App() {
setUpdateInstruction(""); setUpdateInstruction("");
} }
const doCopyCode = useCallback(() => {
copy(generatedCode);
toast.success("Copied to clipboard");
}, [generatedCode]);
return ( return (
<div className="mt-2"> <div className="mt-2">
{IS_RUNNING_ON_CLOUD && <PicoBadge />} {IS_RUNNING_ON_CLOUD && <PicoBadge />}
@ -154,7 +161,7 @@ function App() {
<h1 className="text-2xl ">Screenshot to Code</h1> <h1 className="text-2xl ">Screenshot to Code</h1>
<SettingsDialog settings={settings} setSettings={setSettings} /> <SettingsDialog settings={settings} setSettings={setSettings} />
</div> </div>
{appState === AppStatus.INITIAL && ( {appState === AppState.INITIAL && (
<h2 className="text-sm text-gray-500 mb-2"> <h2 className="text-sm text-gray-500 mb-2">
Drag & drop a screenshot to get started. Drag & drop a screenshot to get started.
</h2> </h2>
@ -162,20 +169,18 @@ function App() {
{IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && <OnboardingNote />} {IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && <OnboardingNote />}
{(appState === AppStatus.CODING || appState === AppStatus.CODE_READY) && ( {(appState === AppState.CODING ||
appState === AppState.CODE_READY) && (
<> <>
{/* Show code preview only when coding */} {/* Show code preview only when coding */}
{appState === AppStatus.CODING && ( {appState === AppState.CODING && (
<div className="flex flex-col"> <div className="flex flex-col">
<div className="flex items-center gap-x-1"> <div className="flex items-center gap-x-1">
<Spinner /> <Spinner />
{executionConsole.slice(-1)[0]} {executionConsole.slice(-1)[0]}
</div> </div>
<div className="flex mt-4 w-full"> <div className="flex mt-4 w-full">
<Button <Button onClick={stop} className="w-full">
onClick={stop}
className="w-full"
>
Stop Stop
</Button> </Button>
</div> </div>
@ -183,7 +188,7 @@ function App() {
</div> </div>
)} )}
{appState === AppStatus.CODE_READY && ( {appState === AppState.CODE_READY && (
<div> <div>
<div className="grid w-full gap-2"> <div className="grid w-full gap-2">
<div className="flex justify-between items-center gap-x-2"> <div className="flex justify-between items-center gap-x-2">
@ -225,7 +230,7 @@ function App() {
<div className="flex flex-col"> <div className="flex flex-col">
<div <div
className={classNames({ className={classNames({
"scanning relative": appState === AppStatus.CODING, "scanning relative": appState === AppState.CODING,
})} })}
> >
<img <img
@ -258,7 +263,7 @@ function App() {
</div> </div>
<main className="py-2 lg:pl-96"> <main className="py-2 lg:pl-96">
{appState === AppStatus.INITIAL && ( {appState === AppState.INITIAL && (
<div className="flex flex-col justify-center items-center gap-y-10"> <div className="flex flex-col justify-center items-center gap-y-10">
<ImageUpload setReferenceImages={doCreate} /> <ImageUpload setReferenceImages={doCreate} />
<UrlInputSection <UrlInputSection
@ -268,7 +273,7 @@ function App() {
</div> </div>
)} )}
{(appState === AppStatus.CODING || appState === AppStatus.CODE_READY) && ( {(appState === AppState.CODING || appState === AppState.CODE_READY) && (
<div className="ml-4"> <div className="ml-4">
<Tabs defaultValue="desktop"> <Tabs defaultValue="desktop">
<div className="flex justify-end mr-8 mb-4"> <div className="flex justify-end mr-8 mb-4">
@ -292,10 +297,19 @@ function App() {
<Preview code={generatedCode} device="mobile" /> <Preview code={generatedCode} device="mobile" />
</TabsContent> </TabsContent>
<TabsContent value="code"> <TabsContent value="code">
<div className="relative">
<CodeMirror <CodeMirror
code={generatedCode} code={generatedCode}
editorTheme={settings.editorTheme} editorTheme={settings.editorTheme}
/> />
<span
title="Copy Code"
className="flex items-center justify-center w-10 h-10 text-gray-500 hover:bg-gray-100 cursor-pointer rounded-lg text-sm p-2.5 absolute top-[20px] right-[20px]"
onClick={doCopyCode}
>
<FaCopy />
</span>
</div>
</TabsContent> </TabsContent>
</Tabs> </Tabs>
</div> </div>

View File

@ -0,0 +1 @@
export const USER_CLOSE_WEB_SOCKET_CODE = 4333;

View File

@ -1,6 +1,6 @@
import toast from "react-hot-toast"; import toast from "react-hot-toast";
import { WS_BACKEND_URL } from "./config"; import { WS_BACKEND_URL } from "./config";
import { USER_CLOSE_WEB_SOCKET_CODE } from "./types"; import { USER_CLOSE_WEB_SOCKET_CODE } from "./constants";
const ERROR_MESSAGE = const ERROR_MESSAGE =
"Error generating code. Check the Developer Console for details. Feel free to open a Github issue"; "Error generating code. Check the Developer Console for details. Feel free to open a Github issue";
@ -53,6 +53,7 @@ export function generateCode(
} else if (event.code !== 1000) { } else if (event.code !== 1000) {
console.error("WebSocket error code", event); console.error("WebSocket error code", event);
toast.error(ERROR_MESSAGE); toast.error(ERROR_MESSAGE);
onComplete();
} }
onComplete(); onComplete();
}); });

View File

@ -5,10 +5,8 @@ export interface Settings {
editorTheme: string; editorTheme: string;
} }
export enum AppStatus { export enum AppState {
INITIAL = "INITIAL", INITIAL = "INITIAL",
CODING = "CODING", CODING = "CODING",
CODE_READY = "CODE_READY", CODE_READY = "CODE_READY",
} }
export const USER_CLOSE_WEB_SOCKET_CODE = 4333;

View File

@ -1263,6 +1263,13 @@ convert-source-map@^2.0.0:
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz" resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz"
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
copy-to-clipboard@^3.3.3:
version "3.3.3"
resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0"
integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==
dependencies:
toggle-selection "^1.0.6"
crelt@^1.0.5: crelt@^1.0.5:
version "1.0.6" version "1.0.6"
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72" resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
@ -2447,6 +2454,11 @@ to-regex-range@^5.0.1:
dependencies: dependencies:
is-number "^7.0.0" is-number "^7.0.0"
toggle-selection@^1.0.6:
version "1.0.6"
resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==
ts-api-utils@^1.0.1: ts-api-utils@^1.0.1:
version "1.0.3" version "1.0.3"
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz" resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz"