import { useEffect, useRef } from "react"; import { generateCode } from "./generateCode"; import SettingsDialog from "./components/settings/SettingsDialog"; import { AppState, CodeGenerationParams, EditorTheme, Settings } from "./types"; import { IS_RUNNING_ON_CLOUD } from "./config"; import { PicoBadge } from "./components/messages/PicoBadge"; import { OnboardingNote } from "./components/messages/OnboardingNote"; import { usePersistedState } from "./hooks/usePersistedState"; import TermsOfServiceDialog from "./components/TermsOfServiceDialog"; import { USER_CLOSE_WEB_SOCKET_CODE } from "./constants"; import { extractHistory } from "./components/history/utils"; import toast from "react-hot-toast"; import { Stack } from "./lib/stacks"; import { CodeGenerationModel } from "./lib/models"; import useBrowserTabIndicator from "./hooks/useBrowserTabIndicator"; import TipLink from "./components/messages/TipLink"; import { useAppStore } from "./store/app-store"; import { useProjectStore } from "./store/project-store"; import Sidebar from "./components/sidebar/Sidebar"; import PreviewPane from "./components/preview/PreviewPane"; import DeprecationMessage from "./components/messages/DeprecationMessage"; import { GenerationSettings } from "./components/settings/GenerationSettings"; import StartPane from "./components/start-pane/StartPane"; import { takeScreenshot } from "./lib/takeScreenshot"; import { Commit, createCommit } from "./components/history/history_types"; function App() { const { // Inputs inputMode, setInputMode, isImportedFromCode, setIsImportedFromCode, referenceImages, setReferenceImages, head, commits, addCommit, removeCommit, setHead, appendCommitCode, setCommitCode, resetCommits, // Outputs appendExecutionConsole, resetExecutionConsoles, } = useProjectStore(); const { disableInSelectAndEditMode, setUpdateInstruction, appState, setAppState, shouldIncludeResultImage, setShouldIncludeResultImage, } = useAppStore(); // Settings const [settings, setSettings] = usePersistedState( { openAiApiKey: null, openAiBaseURL: null, anthropicApiKey: null, screenshotOneApiKey: null, isImageGenerationEnabled: true, editorTheme: EditorTheme.COBALT, generatedCodeConfig: Stack.HTML_TAILWIND, codeGenerationModel: CodeGenerationModel.CLAUDE_3_5_SONNET_2024_06_20, // Only relevant for hosted version isTermOfServiceAccepted: false, }, "setting" ); const wsRef = useRef(null); // Code generation model from local storage or the default value const model = settings.codeGenerationModel || CodeGenerationModel.GPT_4_VISION; const showBetterModelMessage = model !== CodeGenerationModel.GPT_4O_2024_05_13 && model !== CodeGenerationModel.CLAUDE_3_5_SONNET_2024_06_20 && appState === AppState.INITIAL; const showSelectAndEditFeature = (model === CodeGenerationModel.GPT_4O_2024_05_13 || model === CodeGenerationModel.CLAUDE_3_5_SONNET_2024_06_20) && (settings.generatedCodeConfig === Stack.HTML_TAILWIND || settings.generatedCodeConfig === Stack.HTML_CSS); // Indicate coding state using the browser tab's favicon and title useBrowserTabIndicator(appState === AppState.CODING); // 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: Stack.HTML_TAILWIND, })); } }, [settings.generatedCodeConfig, setSettings]); // Functions const reset = () => { setAppState(AppState.INITIAL); setShouldIncludeResultImage(false); setUpdateInstruction(""); disableInSelectAndEditMode(); resetExecutionConsoles(); resetCommits(); // Inputs setInputMode("image"); setReferenceImages([]); setIsImportedFromCode(false); }; const regenerate = () => { // TODO: post to Sentry if (head === null) { toast.error( "No current version set. Please open a Github issue as this shouldn't happen." ); return; } // Retrieve the previous command const currentCommit = commits[head]; if (currentCommit.type !== "ai_create") { toast.error("Only the first version can be regenerated."); return; } // Re-run the create doCreate(referenceImages, inputMode); }; // Used when the user cancels the code generation const cancelCodeGeneration = () => { wsRef.current?.close?.(USER_CLOSE_WEB_SOCKET_CODE); // make sure stop can correct the state even if the websocket is already closed // TODO: Look into this // cancelCodeGenerationAndReset(); }; // Used for code generation failure as well const cancelCodeGenerationAndReset = (commit: Commit) => { // When the current commit is the first version, reset the entire app state if (commit.type === "ai_create") { reset(); } else { // Otherwise, remove current commit from commits removeCommit(commit.hash); // Revert to parent commit const parentCommitHash = commit.parentHash; if (parentCommitHash) { setHead(parentCommitHash); } else { // TODO: Hit Sentry } setAppState(AppState.CODE_READY); } }; function doGenerateCode(params: CodeGenerationParams) { // Reset the execution console resetExecutionConsoles(); // Set the app state setAppState(AppState.CODING); // Merge settings with params const updatedParams = { ...params, ...settings }; const baseCommitObject = { date_created: new Date(), variants: [{ code: "" }, { code: "" }], selectedVariantIndex: 0, }; const commitInputObject = params.generationType === "create" ? { ...baseCommitObject, type: "ai_create" as const, parentHash: null, inputs: { image_url: referenceImages[0] }, } : { ...baseCommitObject, type: "ai_edit" as const, parentHash: head, inputs: { prompt: params.history ? params.history[params.history.length - 1] : "", }, }; const commit = createCommit(commitInputObject); addCommit(commit); setHead(commit.hash); generateCode( wsRef, updatedParams, // On change (token, variant) => { appendCommitCode(commit.hash, variant, token); }, // On set code (code, variant) => { setCommitCode(commit.hash, variant, code); }, // On status update (line, variant) => appendExecutionConsole(variant, line), // On cancel () => { cancelCodeGenerationAndReset(commit); }, // On complete () => { setAppState(AppState.CODE_READY); } ); } // Initial version creation function doCreate(referenceImages: string[], inputMode: "image" | "video") { // Reset any existing state reset(); // Set the input states setReferenceImages(referenceImages); setInputMode(inputMode); // Kick off the code generation if (referenceImages.length > 0) { doGenerateCode({ generationType: "create", image: referenceImages[0], inputMode, }); } } // Subsequent updates async function doUpdate( updateInstruction: string, selectedElement?: HTMLElement ) { if (updateInstruction.trim() === "") { toast.error("Please include some instructions for AI on what to update."); return; } // if (currentVersion === null) { // toast.error( // "No current version set. Contact support or open a Github issue." // ); // return; // } let historyTree; try { // TODO: Fix head being null historyTree = extractHistory(head || "", commits); } catch { toast.error( "Version history is invalid. This shouldn't happen. Please contact support or open a Github issue." ); return; } let modifiedUpdateInstruction = updateInstruction; // Send in a reference to the selected element if it exists if (selectedElement) { modifiedUpdateInstruction = updateInstruction + " referring to this element specifically: " + selectedElement.outerHTML; } const updatedHistory = [...historyTree, modifiedUpdateInstruction]; console.log(updatedHistory); if (shouldIncludeResultImage) { const resultImage = await takeScreenshot(); doGenerateCode({ generationType: "update", inputMode, image: referenceImages[0], resultImage: resultImage, history: updatedHistory, isImportedFromCode, }); } else { doGenerateCode({ generationType: "update", inputMode, image: referenceImages[0], history: updatedHistory, isImportedFromCode, }); } setUpdateInstruction(""); } const handleTermDialogOpenChange = (open: boolean) => { setSettings((s) => ({ ...s, isTermOfServiceAccepted: !open, })); }; function setStack(stack: Stack) { setSettings((prev) => ({ ...prev, generatedCodeConfig: stack, })); } function importFromCode(code: string, stack: Stack) { // Set input state setIsImportedFromCode(true); console.log(code); // Set up this project // TODO* // setGeneratedCode(code); setStack(stack); // setAppHistory([ // { // type: "code_create", // parentIndex: null, // code, // inputs: { code }, // }, // ]); // setVariant(0, { // type: "code_create", // parentIndex: null, // code, // }); // setCurrentVariantIndex(0); // setCurrentVersion(0); // Set the app state setAppState(AppState.CODE_READY); } return (
{IS_RUNNING_ON_CLOUD && } {IS_RUNNING_ON_CLOUD && ( )}
{/* Header with access to settings */}

Screenshot to Code

{/* Generation settings like stack and model */} {/* Show auto updated message when older models are choosen */} {showBetterModelMessage && } {/* Show tip link until coding is complete */} {appState !== AppState.CODE_READY && } {IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && } {/* Rest of the sidebar when we're not in the initial state */} {(appState === AppState.CODING || appState === AppState.CODE_READY) && ( )}
{appState === AppState.INITIAL && ( )} {(appState === AppState.CODING || appState === AppState.CODE_READY) && ( )}
); } export default App;