Spaces:
Running
Running
| "use client"; | |
| import CodeViewer from "@/components/code-viewer"; | |
| import { useScrollTo } from "@/hooks/use-scroll-to"; | |
| import { CheckIcon } from "@heroicons/react/16/solid"; | |
| import { ArrowLongRightIcon, ChevronDownIcon } from "@heroicons/react/20/solid"; | |
| import { AnimatePresence, motion } from "framer-motion"; | |
| import { FormEvent, useEffect, useState } from "react"; | |
| import LoadingDots from "../../components/loading-dots"; | |
| import * as Select from "@radix-ui/react-select"; | |
| function removeCodeFormatting(code: string): string { | |
| return code.replace(/```(?:typescript|javascript|tsx)?\n([\s\S]*?)```/g, "$1").trim(); | |
| } | |
| export default function Home() { | |
| const [status, setStatus] = useState<"initial" | "creating" | "created">("initial"); | |
| const [prompt, setPrompt] = useState(""); | |
| const [model, setModel] = useState("gemini-2.0-flash-exp"); | |
| const [generatedCode, setGeneratedCode] = useState(""); | |
| const [ref, scrollTo] = useScrollTo(); | |
| const models = [ | |
| { label: "gemini-2.0-flash-exp", value: "gemini-2.0-flash-exp" }, | |
| { label: "gemini-1.5-pro", value: "gemini-1.5-pro" }, | |
| { label: "gemini-1.5-flash", value: "gemini-1.5-flash" }, | |
| ]; | |
| const loading = status === "creating"; | |
| async function createApp(e: FormEvent<HTMLFormElement>) { | |
| e.preventDefault(); | |
| setStatus("creating"); | |
| setGeneratedCode(""); | |
| try { | |
| const res = await fetch("/api/generateCode", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify({ | |
| model, | |
| messages: [{ role: "user", content: prompt }], | |
| }), | |
| }); | |
| if (!res.ok) { | |
| const errorBody = await res.text(); | |
| throw new Error(`HTTP Error ${res.status}: ${res.statusText}. ${errorBody}`); | |
| } | |
| const reader = res.body?.getReader(); | |
| if (!reader) throw new Error("The response does not contain a body."); | |
| let receivedData = ""; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| receivedData += new TextDecoder().decode(value); | |
| } | |
| const cleanedData = removeCodeFormatting(receivedData); | |
| setGeneratedCode(cleanedData); | |
| setStatus("created"); | |
| } catch (error: any) { | |
| console.error("Error during code generation:", error.message); | |
| setStatus("initial"); | |
| } | |
| } | |
| useEffect(() => { | |
| const el = document.querySelector(".cm-scroller"); | |
| if (el && loading) { | |
| const end = el.scrollHeight - el.clientHeight; | |
| el.scrollTo({ top: end }); | |
| } | |
| }, [loading, generatedCode]); | |
| return ( | |
| <main className="mt-12 flex w-full flex-1 flex-col items-center px-4 text-center sm:mt-1"> | |
| <a | |
| className="mb-4 inline-flex h-7 items-center rounded-3xl bg-gray-300/50 px-7 py-5 shadow-sm dark:bg-gray-800/50" | |
| href="https://ai.google.dev/gemini-api/docs" | |
| target="_blank" | |
| > | |
| Powered by <span className="font-medium">Gemini API</span> | |
| </a> | |
| <h1 className="my-6 max-w-3xl text-4xl font-bold text-gray-800 sm:text-6xl"> | |
| Turn your <span className="text-blue-600">idea</span> | |
| <br /> into an <span className="text-blue-600">app</span> | |
| </h1> | |
| <form className="w-full max-w-xl" onSubmit={createApp}> | |
| <fieldset disabled={loading} className="disabled:opacity-75"> | |
| <div className="relative mt-5"> | |
| <div className="relative flex bg-white shadow-md rounded-xl"> | |
| <textarea | |
| rows={3} | |
| required | |
| value={prompt} | |
| onChange={(e) => setPrompt(e.target.value)} | |
| className="w-full resize-none rounded-l-xl p-4" | |
| placeholder="Build me a calculator app..." | |
| /> | |
| <button | |
| type="submit" | |
| disabled={loading} | |
| className="bg-blue-500 text-white px-5 rounded-r-xl" | |
| > | |
| {status === "creating" ? <LoadingDots /> : <ArrowLongRightIcon />} | |
| </button> | |
| </div> | |
| </div> | |
| <div className="mt-6 flex items-center gap-4"> | |
| <label className="text-gray-500">Model:</label> | |
| <Select.Root value={model} onValueChange={(value) => setModel(value)}> | |
| <Select.Trigger className="p-2 bg-gray-100 rounded-md">{model}</Select.Trigger> | |
| <Select.Content> | |
| {models.map((model) => ( | |
| <Select.Item key={model.value} value={model.value}> | |
| {model.label} | |
| </Select.Item> | |
| ))} | |
| </Select.Content> | |
| </Select.Root> | |
| </div> | |
| </fieldset> | |
| </form> | |
| {status !== "initial" && ( | |
| <motion.div initial={{ height: 0 }} animate={{ height: "auto" }} className="w-full"> | |
| <CodeViewer code={generatedCode} /> | |
| </motion.div> | |
| )} | |
| </main> | |
| ); | |
| } | |