diff --git a/.gitattributes b/.gitattributes index a6344aac8c09253b3b630fb776ae94478aa0275b..40af412df5bf517be336b9833bbb79e9b17f4f64 100644 --- a/.gitattributes +++ b/.gitattributes @@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text *.zip filter=lfs diff=lfs merge=lfs -text *.zst filter=lfs diff=lfs merge=lfs -text *tfevents* filter=lfs diff=lfs merge=lfs -text +public/halo.png filter=lfs diff=lfs merge=lfs -text diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..71c2efb52750faed351688d3352879ae5c3fbaa7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,20 @@ +# Use Node 22 as the base image +FROM node:22-alpine + +# Set the working directory inside the container +WORKDIR /app + +# Copy package.json and package-lock.json (if you have one) +COPY package*.json ./ + +# Install dependencies +RUN npm install + +# Copy the rest of the application code +COPY . . + +# Expose the port your React app runs on +EXPOSE 7860 + +# Command to run the application +CMD ["npm", "run", "dev"] \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..9882e628435f2d186a49637c31b9d9a3bfb79745 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Hassan El Mghari + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index df42be21ff205b770c0bae1e3a85e2b56a2f6ccb..ba278fc5d9a4182e2be677843900dde17ba0454b 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,26 @@ ---- -title: Gemini Coder -emoji: 🦀 -colorFrom: yellow -colorTo: green -sdk: docker -pinned: false ---- - -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference + + Gemini Coder +

Gemini Coder

+
+ +

+ Generate small apps with one prompt. Powered by the Gemini API. +

+ +This project is fully based on [llamacoder](https://github.com/Nutlope/llamacoder). Please follow [Nutlope](https://github.com/Nutlope) and give them a star.. + +## Tech stack + +- [Gemini API](https://ai.google.dev/gemini-api/docs) to use Gemini 1.5 Pro, Gemini 1.5 Flash, and Gemini 2.0 Flash Experimental +- [Sandpack](https://sandpack.codesandbox.io/) for the code sandbox +- Next.js app router with Tailwind + +You can also experiment with Gemini in [Google AI Studio](https://aistudio.google.com/). + +## Cloning & running + +1. Clone the repo: `git clone https://github.com/osanseviero/geminicoder` +2. Create a `.env` file and add your [Google AI Studio API key](https://aistudio.google.com/app/apikey): `GOOGLE_AI_API_KEY=` +3. Run `npm install` and `npm run dev` to install dependencies and run locally + +**This is a personal project and not a Google official project** \ No newline at end of file diff --git a/app/(main)/layout.tsx b/app/(main)/layout.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9cbd6b3cb11d3df9060ec258a4a75b7033219965 --- /dev/null +++ b/app/(main)/layout.tsx @@ -0,0 +1,31 @@ +import Image from "next/image"; +import bgImg from "@/public/halo.png"; +import Footer from "@/components/Footer"; +import Header from "@/components/Header"; + +export default function Layout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + +
+ +
+ +
+
+
+ {children} +
+
+ + ); +} diff --git a/app/(main)/page.tsx b/app/(main)/page.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9ef19ae9880eedd2388638cac4bad6bb508ca489 --- /dev/null +++ b/app/(main)/page.tsx @@ -0,0 +1,268 @@ +"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 { ArrowUpOnSquareIcon } from "@heroicons/react/24/outline"; +import * as Select from "@radix-ui/react-select"; +import * as Switch from "@radix-ui/react-switch"; +import { AnimatePresence, motion } from "framer-motion"; +import { FormEvent, useEffect, useState } from "react"; +import LoadingDots from "../../components/loading-dots"; + +function removeCodeFormatting(code: string): string { + return code.replace(/```(?:typescript|javascript|tsx)?\n([\s\S]*?)```/g, '$1').trim(); +} + +export default function Home() { + let [status, setStatus] = useState< + "initial" | "creating" | "created" | "updating" | "updated" + >("initial"); + let [prompt, setPrompt] = useState(""); + let 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", + } + ]; + let [model, setModel] = useState(models[0].value); + let [shadcn, setShadcn] = useState(false); + let [modification, setModification] = useState(""); + let [generatedCode, setGeneratedCode] = useState(""); + let [initialAppConfig, setInitialAppConfig] = useState({ + model: "", + shadcn: true, + }); + let [ref, scrollTo] = useScrollTo(); + let [messages, setMessages] = useState<{ role: string; content: string }[]>( + [], + ); + + let loading = status === "creating" || status === "updating"; + + async function createApp(e: FormEvent) { + e.preventDefault(); + + if (status !== "initial") { + scrollTo({ delay: 0.5 }); + } + + setStatus("creating"); + setGeneratedCode(""); + + let res = await fetch("/api/generateCode", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + model, + shadcn, + messages: [{ role: "user", content: prompt }], + }), + }); + + if (!res.ok) { + throw new Error(res.statusText); + } + + if (!res.body) { + throw new Error("No response body"); + } + + const reader = res.body.getReader(); + let receivedData = ""; + + while (true) { + const { done, value } = await reader.read(); + if (done) { + break; + } + receivedData += new TextDecoder().decode(value); + const cleanedData = removeCodeFormatting(receivedData); + setGeneratedCode(cleanedData); + } + + setMessages([{ role: "user", content: prompt }]); + setInitialAppConfig({ model, shadcn }); + setStatus("created"); + } + + useEffect(() => { + let el = document.querySelector(".cm-scroller"); + if (el && loading) { + let end = el.scrollHeight - el.clientHeight; + el.scrollTo({ top: end }); + } + }, [loading, generatedCode]); + + return ( +
+ + + Powered by Gemini API + + +

+ Turn your idea +
into an app +

+ +
+
+
+
+
+
+