Spaces:
Running
Running
File size: 4,926 Bytes
63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 ff9c083 63c7991 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
"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>
);
}
|