In4ctividad's picture
Update app/(main)/page.tsx
ff9c083 verified
raw
history blame
4.93 kB
"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>
);
}