import { useEffect, useRef, useState } from "react"; import ImageUpload from "./components/ImageUpload"; import CodePreview from "./components/CodePreview"; import Preview from "./components/Preview"; import { generateCode } from "./generateCode"; import Spinner from "./components/Spinner"; import classNames from "classnames"; import { FaCode, FaDesktop, FaDownload, FaMobile, FaUndo, } from "react-icons/fa"; import { Switch } from "./components/ui/switch"; import { Button } from "@/components/ui/button"; import { Textarea } from "@/components/ui/textarea"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs"; import SettingsDialog from "./components/SettingsDialog"; import { AppState, CodeGenerationParams, EditorTheme, GeneratedCodeConfig, Settings, } from "./types"; import { IS_RUNNING_ON_CLOUD } from "./config"; import { PicoBadge } from "./components/PicoBadge"; import { OnboardingNote } from "./components/OnboardingNote"; import { usePersistedState } from "./hooks/usePersistedState"; import { UrlInputSection } from "./components/UrlInputSection"; import TermsOfServiceDialog from "./components/TermsOfServiceDialog"; import html2canvas from "html2canvas"; import { USER_CLOSE_WEB_SOCKET_CODE } from "./constants"; import CodeTab from "./components/CodeTab"; import OutputSettingsSection from "./components/OutputSettingsSection"; import { addEvent } from "./lib/analytics"; 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; function App() { const [appState, setAppState] = useState(AppState.INITIAL); const [generatedCode, setGeneratedCode] = useState(""); const [referenceImages, setReferenceImages] = useState([]); const [executionConsole, setExecutionConsole] = useState([]); const [updateInstruction, setUpdateInstruction] = useState(""); const [isImportedFromCode, setIsImportedFromCode] = useState(false); // Settings const [settings, setSettings] = usePersistedState( { openAiApiKey: null, openAiBaseURL: null, screenshotOneApiKey: null, isImageGenerationEnabled: true, editorTheme: EditorTheme.COBALT, generatedCodeConfig: GeneratedCodeConfig.HTML_TAILWIND, // Only relevant for hosted version isTermOfServiceAccepted: false, accessCode: null, }, "setting" ); // App history const [appHistory, setAppHistory] = useState([]); // Tracks the currently shown version from app history const [currentVersion, setCurrentVersion] = useState(null); const [shouldIncludeResultImage, setShouldIncludeResultImage] = useState(false); const wsRef = useRef(null); // When the user already has the settings in local storage, newly added keys // do not get added to the settings so if it's falsy, we populate it with the default // value useEffect(() => { if (!settings.generatedCodeConfig) { setSettings((prev) => ({ ...prev, generatedCodeConfig: GeneratedCodeConfig.HTML_TAILWIND, })); } }, [settings.generatedCodeConfig, setSettings]); const takeScreenshot = async (): Promise => { const iframeElement = document.querySelector( "#preview-desktop" ) as HTMLIFrameElement; if (!iframeElement?.contentWindow?.document.body) { return ""; } const canvas = await html2canvas(iframeElement.contentWindow.document.body); const png = canvas.toDataURL("image/png"); return png; }; const downloadCode = () => { addEvent("Download"); // Create a blob from the generated code const blob = new Blob([generatedCode], { type: "text/html" }); const url = URL.createObjectURL(blob); // Create an anchor element and set properties for download const a = document.createElement("a"); a.href = url; a.download = "index.html"; // Set the file name for download document.body.appendChild(a); // Append to the document a.click(); // Programmatically click the anchor to trigger download // Clean up by removing the anchor and revoking the Blob URL document.body.removeChild(a); URL.revokeObjectURL(url); }; const reset = () => { setAppState(AppState.INITIAL); setGeneratedCode(""); setReferenceImages([]); setExecutionConsole([]); setAppHistory([]); setCurrentVersion(null); setIsImportedFromCode(false); }; const cancelCodeGeneration = () => { wsRef.current?.close?.(USER_CLOSE_WEB_SOCKET_CODE); // make sure stop can correct the state even if the websocket is already closed cancelCodeGenerationAndReset(); }; const cancelCodeGenerationAndReset = () => { // When this is the first version, reset the entire app state if (currentVersion === null) { reset(); } else { // Otherwise, revert to the last version setGeneratedCode(appHistory[currentVersion].code); setAppState(AppState.CODE_READY); } }; function doGenerateCode( params: CodeGenerationParams, parentVersion: number | null ) { setExecutionConsole([]); setAppState(AppState.CODING); // Merge settings with params const updatedParams = { ...params, ...settings }; generateCode( wsRef, updatedParams, (token) => setGeneratedCode((prev) => prev + token), (code) => { setGeneratedCode(code); if (params.generationType === "create") { setAppHistory([ { type: "ai_create", parentIndex: null, code, inputs: { image_url: referenceImages[0] }, }, ]); setCurrentVersion(0); } else { setAppHistory((prev) => { // Validate parent version if (parentVersion === null) { toast.error( "No parent version set. Contact support or open a Github issue." ); addEvent("ParentVersionNull"); return prev; } const newHistory: History = [ ...prev, { type: "ai_edit", parentIndex: parentVersion, code, inputs: { prompt: updateInstruction, }, }, ]; setCurrentVersion(newHistory.length - 1); return newHistory; }); } }, (line) => setExecutionConsole((prev) => [...prev, line]), // On cancel () => { cancelCodeGenerationAndReset(); }, // On complete () => { setAppState(AppState.CODE_READY); } ); } // Initial version creation function doCreate(referenceImages: string[]) { // Reset any existing state reset(); setReferenceImages(referenceImages); if (referenceImages.length > 0) { doGenerateCode( { generationType: "create", image: referenceImages[0], }, currentVersion ); } } // Subsequent updates async function doUpdate() { if (currentVersion === null) { toast.error( "No current version set. Contact support or open a Github issue." ); addEvent("CurrentVersionNull"); return; } let historyTree; try { historyTree = extractHistoryTree(appHistory, currentVersion); addEvent("HistoryTreeFailed"); } catch { toast.error( "Version history is invalid. This shouldn't happen. Please contact support or open a Github issue." ); return; } const updatedHistory = [...historyTree, updateInstruction]; if (shouldIncludeResultImage) { const resultImage = await takeScreenshot(); doGenerateCode( { generationType: "update", image: referenceImages[0], resultImage: resultImage, history: updatedHistory, isImportedFromCode, }, currentVersion ); } else { doGenerateCode( { generationType: "update", image: referenceImages[0], history: updatedHistory, isImportedFromCode, }, currentVersion ); } setGeneratedCode(""); setUpdateInstruction(""); } const handleTermDialogOpenChange = (open: boolean) => { setSettings((s) => ({ ...s, isTermOfServiceAccepted: !open, })); }; // TODO: Rename everything to "stack" instead of "config" function setStack(stack: GeneratedCodeConfig) { setSettings((prev) => ({ ...prev, generatedCodeConfig: stack, })); } function importFromCode(code: string, stack: GeneratedCodeConfig) { setIsImportedFromCode(true); // Set up this project setGeneratedCode(code); setStack(stack); setAppHistory([ { type: "code_create", parentIndex: null, code, inputs: { code }, }, ]); setCurrentVersion(0); setAppState(AppState.CODE_READY); } return (
{IS_RUNNING_ON_CLOUD && } {IS_RUNNING_ON_CLOUD && ( )}

Screenshot to Code

setStack(config)} shouldDisableUpdates={ appState === AppState.CODING || appState === AppState.CODE_READY } /> {IS_RUNNING_ON_CLOUD && !(settings.openAiApiKey || settings.accessCode) && ( )} {IS_OPENAI_DOWN && (
OpenAI API is currently down. Try back in 30 minutes or later. We apologize for the inconvenience.
)} {(appState === AppState.CODING || appState === AppState.CODE_READY) && ( <> {/* Show code preview only when coding */} {appState === AppState.CODING && (
{executionConsole.slice(-1)[0]}
)} {appState === AppState.CODE_READY && (