osanseviero commited on
Commit
63c7991
1 Parent(s): 44edc3e

Upload 54 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +1 -0
  2. Dockerfile +20 -0
  3. LICENSE +21 -0
  4. README.md +26 -10
  5. app/(main)/layout.tsx +31 -0
  6. app/(main)/page.tsx +268 -0
  7. app/api/generateCode/route.ts +102 -0
  8. app/api/og/route.tsx +34 -0
  9. app/favicon.ico +0 -0
  10. app/globals.css +24 -0
  11. app/layout.tsx +44 -0
  12. app/robots.txt +1 -0
  13. app/share/[id]/layout.tsx +3 -0
  14. app/share/[id]/page.tsx +52 -0
  15. components/Footer.tsx +57 -0
  16. components/Header.tsx +25 -0
  17. components/code-viewer.css +7 -0
  18. components/code-viewer.tsx +157 -0
  19. components/github-icon.tsx +19 -0
  20. components/loading-dots.module.css +69 -0
  21. components/loading-dots.tsx +17 -0
  22. hooks/use-scroll-to.ts +24 -0
  23. lib/prisma.ts +10 -0
  24. next-env.d.ts +5 -0
  25. next.config.mjs +4 -0
  26. package-lock.json +0 -0
  27. package.json +45 -0
  28. postcss.config.mjs +8 -0
  29. prisma/migrations/20240808195804_first/migration.sql +13 -0
  30. prisma/migrations/migration_lock.toml +3 -0
  31. prisma/schema.prisma +18 -0
  32. public/Aeonik/Aeonik-Bold.ttf +0 -0
  33. public/Aeonik/Aeonik-Medium.ttf +0 -0
  34. public/Aeonik/Aeonik-Regular.ttf +0 -0
  35. public/Aeonik/AeonikFono-Bold.otf +0 -0
  36. public/Aeonik/AeonikFono-Regular.otf +0 -0
  37. public/Aeonik/AeonikMono-Regular.otf +0 -0
  38. public/cssIcon.png +0 -0
  39. public/favicon.ico +0 -0
  40. public/halo.png +3 -0
  41. public/logo.svg +10 -0
  42. tailwind.config.ts +25 -0
  43. tsconfig.json +26 -0
  44. utils/domain.ts +10 -0
  45. utils/shadcn-docs/avatar.tsx +12 -0
  46. utils/shadcn-docs/button.tsx +14 -0
  47. utils/shadcn-docs/card.tsx +27 -0
  48. utils/shadcn-docs/checkbox.tsx +9 -0
  49. utils/shadcn-docs/index.ts +23 -0
  50. utils/shadcn-docs/input.tsx +9 -0
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ public/halo.png filter=lfs diff=lfs merge=lfs -text
Dockerfile ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use Node 22 as the base image
2
+ FROM node:22-alpine
3
+
4
+ # Set the working directory inside the container
5
+ WORKDIR /app
6
+
7
+ # Copy package.json and package-lock.json (if you have one)
8
+ COPY package*.json ./
9
+
10
+ # Install dependencies
11
+ RUN npm install
12
+
13
+ # Copy the rest of the application code
14
+ COPY . .
15
+
16
+ # Expose the port your React app runs on
17
+ EXPOSE 7860
18
+
19
+ # Command to run the application
20
+ CMD ["npm", "run", "dev"]
LICENSE ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Hassan El Mghari
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
README.md CHANGED
@@ -1,10 +1,26 @@
1
- ---
2
- title: Gemini Coder
3
- emoji: 🦀
4
- colorFrom: yellow
5
- colorTo: green
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <a href="https://www.geminicoder.io">
2
+ <img alt="Gemini Coder" src="./public/logo.svg">
3
+ <h1 align="center">Gemini Coder</h1>
4
+ </a>
5
+
6
+ <p align="center">
7
+ Generate small apps with one prompt. Powered by the Gemini API.
8
+ </p>
9
+
10
+ This project is fully based on [llamacoder](https://github.com/Nutlope/llamacoder). Please follow [Nutlope](https://github.com/Nutlope) and give them a star..
11
+
12
+ ## Tech stack
13
+
14
+ - [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
15
+ - [Sandpack](https://sandpack.codesandbox.io/) for the code sandbox
16
+ - Next.js app router with Tailwind
17
+
18
+ You can also experiment with Gemini in [Google AI Studio](https://aistudio.google.com/).
19
+
20
+ ## Cloning & running
21
+
22
+ 1. Clone the repo: `git clone https://github.com/osanseviero/geminicoder`
23
+ 2. Create a `.env` file and add your [Google AI Studio API key](https://aistudio.google.com/app/apikey): `GOOGLE_AI_API_KEY=`
24
+ 3. Run `npm install` and `npm run dev` to install dependencies and run locally
25
+
26
+ **This is a personal project and not a Google official project**
app/(main)/layout.tsx ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Image from "next/image";
2
+ import bgImg from "@/public/halo.png";
3
+ import Footer from "@/components/Footer";
4
+ import Header from "@/components/Header";
5
+
6
+ export default function Layout({
7
+ children,
8
+ }: Readonly<{
9
+ children: React.ReactNode;
10
+ }>) {
11
+ return (
12
+ <body className="bg-brand antialiased">
13
+ <div className="absolute inset-x-0 flex justify-center">
14
+ <Image
15
+ src={bgImg}
16
+ alt=""
17
+ className="w-full max-w-[1200px] mix-blend-screen"
18
+ priority
19
+ />
20
+ </div>
21
+
22
+ <div className="isolate">
23
+ <div className="mx-auto flex min-h-screen max-w-7xl flex-col items-center justify-center py-2">
24
+ <Header />
25
+ {children}
26
+ <Footer />
27
+ </div>
28
+ </div>
29
+ </body>
30
+ );
31
+ }
app/(main)/page.tsx ADDED
@@ -0,0 +1,268 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import CodeViewer from "@/components/code-viewer";
4
+ import { useScrollTo } from "@/hooks/use-scroll-to";
5
+ import { CheckIcon } from "@heroicons/react/16/solid";
6
+ import { ArrowLongRightIcon, ChevronDownIcon } from "@heroicons/react/20/solid";
7
+ import { ArrowUpOnSquareIcon } from "@heroicons/react/24/outline";
8
+ import * as Select from "@radix-ui/react-select";
9
+ import * as Switch from "@radix-ui/react-switch";
10
+ import { AnimatePresence, motion } from "framer-motion";
11
+ import { FormEvent, useEffect, useState } from "react";
12
+ import LoadingDots from "../../components/loading-dots";
13
+
14
+ function removeCodeFormatting(code: string): string {
15
+ return code.replace(/```(?:typescript|javascript|tsx)?\n([\s\S]*?)```/g, '$1').trim();
16
+ }
17
+
18
+ export default function Home() {
19
+ let [status, setStatus] = useState<
20
+ "initial" | "creating" | "created" | "updating" | "updated"
21
+ >("initial");
22
+ let [prompt, setPrompt] = useState("");
23
+ let models = [
24
+ {
25
+ label: "gemini-2.0-flash-exp",
26
+ value: "gemini-2.0-flash-exp",
27
+ },
28
+ {
29
+ label: "gemini-1.5-pro",
30
+ value: "gemini-1.5-pro",
31
+ },
32
+ {
33
+ label: "gemini-1.5-flash",
34
+ value: "gemini-1.5-flash",
35
+ }
36
+ ];
37
+ let [model, setModel] = useState(models[0].value);
38
+ let [shadcn, setShadcn] = useState(false);
39
+ let [modification, setModification] = useState("");
40
+ let [generatedCode, setGeneratedCode] = useState("");
41
+ let [initialAppConfig, setInitialAppConfig] = useState({
42
+ model: "",
43
+ shadcn: true,
44
+ });
45
+ let [ref, scrollTo] = useScrollTo();
46
+ let [messages, setMessages] = useState<{ role: string; content: string }[]>(
47
+ [],
48
+ );
49
+
50
+ let loading = status === "creating" || status === "updating";
51
+
52
+ async function createApp(e: FormEvent<HTMLFormElement>) {
53
+ e.preventDefault();
54
+
55
+ if (status !== "initial") {
56
+ scrollTo({ delay: 0.5 });
57
+ }
58
+
59
+ setStatus("creating");
60
+ setGeneratedCode("");
61
+
62
+ let res = await fetch("/api/generateCode", {
63
+ method: "POST",
64
+ headers: {
65
+ "Content-Type": "application/json",
66
+ },
67
+ body: JSON.stringify({
68
+ model,
69
+ shadcn,
70
+ messages: [{ role: "user", content: prompt }],
71
+ }),
72
+ });
73
+
74
+ if (!res.ok) {
75
+ throw new Error(res.statusText);
76
+ }
77
+
78
+ if (!res.body) {
79
+ throw new Error("No response body");
80
+ }
81
+
82
+ const reader = res.body.getReader();
83
+ let receivedData = "";
84
+
85
+ while (true) {
86
+ const { done, value } = await reader.read();
87
+ if (done) {
88
+ break;
89
+ }
90
+ receivedData += new TextDecoder().decode(value);
91
+ const cleanedData = removeCodeFormatting(receivedData);
92
+ setGeneratedCode(cleanedData);
93
+ }
94
+
95
+ setMessages([{ role: "user", content: prompt }]);
96
+ setInitialAppConfig({ model, shadcn });
97
+ setStatus("created");
98
+ }
99
+
100
+ useEffect(() => {
101
+ let el = document.querySelector(".cm-scroller");
102
+ if (el && loading) {
103
+ let end = el.scrollHeight - el.clientHeight;
104
+ el.scrollTo({ top: end });
105
+ }
106
+ }, [loading, generatedCode]);
107
+
108
+ return (
109
+ <main className="mt-12 flex w-full flex-1 flex-col items-center px-4 text-center sm:mt-1">
110
+ <a
111
+ className="mb-4 inline-flex h-7 shrink-0 items-center gap-[9px] rounded-[50px] border-[0.5px] border-solid border-[#E6E6E6] bg-[rgba(234,238,255,0.65)] bg-gray-100 px-7 py-5 shadow-[0px_1px_1px_0px_rgba(0,0,0,0.25)]"
112
+ href="https://ai.google.dev/gemini-api/docs"
113
+ target="_blank"
114
+ >
115
+ <span className="text-center">
116
+ Powered by <span className="font-medium">Gemini API</span>
117
+ </span>
118
+ </a>
119
+ <h1 className="my-6 max-w-3xl text-4xl font-bold text-gray-800 sm:text-6xl">
120
+ Turn your <span className="text-blue-600">idea</span>
121
+ <br /> into an <span className="text-blue-600">app</span>
122
+ </h1>
123
+
124
+ <form className="w-full max-w-xl" onSubmit={createApp}>
125
+ <fieldset disabled={loading} className="disabled:opacity-75">
126
+ <div className="relative mt-5">
127
+ <div className="absolute -inset-2 rounded-[32px] bg-gray-300/50" />
128
+ <div className="relative flex rounded-3xl bg-white shadow-sm">
129
+ <div className="relative flex flex-grow items-stretch focus-within:z-10">
130
+ <textarea
131
+ rows={3}
132
+ required
133
+ value={prompt}
134
+ onChange={(e) => setPrompt(e.target.value)}
135
+ name="prompt"
136
+ className="w-full resize-none rounded-l-3xl bg-transparent px-6 py-5 text-lg focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500"
137
+ placeholder="Build me a calculator app..."
138
+ />
139
+ </div>
140
+ <button
141
+ type="submit"
142
+ disabled={loading}
143
+ className="relative -ml-px inline-flex items-center gap-x-1.5 rounded-r-3xl px-3 py-2 text-sm font-semibold text-blue-500 hover:text-blue-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500 disabled:text-gray-900"
144
+ >
145
+ {status === "creating" ? (
146
+ <LoadingDots color="black" style="large" />
147
+ ) : (
148
+ <ArrowLongRightIcon className="-ml-0.5 size-6" />
149
+ )}
150
+ </button>
151
+ </div>
152
+ </div>
153
+ <div className="mt-6 flex flex-col justify-center gap-4 sm:flex-row sm:items-center sm:gap-8">
154
+ <div className="flex items-center justify-between gap-3 sm:justify-center">
155
+ <p className="text-gray-500 sm:text-xs">Model:</p>
156
+ <Select.Root
157
+ name="model"
158
+ disabled={loading}
159
+ value={model}
160
+ onValueChange={(value) => setModel(value)}
161
+ >
162
+ <Select.Trigger className="group flex w-60 max-w-xs items-center rounded-2xl border-[6px] border-gray-300 bg-white px-4 py-2 text-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500">
163
+ <Select.Value />
164
+ <Select.Icon className="ml-auto">
165
+ <ChevronDownIcon className="size-6 text-gray-300 group-focus-visible:text-gray-500 group-enabled:group-hover:text-gray-500" />
166
+ </Select.Icon>
167
+ </Select.Trigger>
168
+ <Select.Portal>
169
+ <Select.Content className="overflow-hidden rounded-md bg-white shadow-lg">
170
+ <Select.Viewport className="p-2">
171
+ {models.map((model) => (
172
+ <Select.Item
173
+ key={model.value}
174
+ value={model.value}
175
+ className="flex cursor-pointer items-center rounded-md px-3 py-2 text-sm data-[highlighted]:bg-gray-100 data-[highlighted]:outline-none"
176
+ >
177
+ <Select.ItemText asChild>
178
+ <span className="inline-flex items-center gap-2 text-gray-500">
179
+ <div className="size-2 rounded-full bg-green-500" />
180
+ {model.label}
181
+ </span>
182
+ </Select.ItemText>
183
+ <Select.ItemIndicator className="ml-auto">
184
+ <CheckIcon className="size-5 text-blue-600" />
185
+ </Select.ItemIndicator>
186
+ </Select.Item>
187
+ ))}
188
+ </Select.Viewport>
189
+ <Select.ScrollDownButton />
190
+ <Select.Arrow />
191
+ </Select.Content>
192
+ </Select.Portal>
193
+ </Select.Root>
194
+ </div>
195
+
196
+ <div className="flex h-full items-center justify-between gap-3 sm:justify-center">
197
+ <label className="text-gray-500 sm:text-xs" htmlFor="shadcn">
198
+ shadcn/ui:
199
+ </label>
200
+ <Switch.Root
201
+ className="group flex w-20 max-w-xs items-center rounded-2xl border-[6px] border-gray-300 bg-white p-1.5 text-sm shadow-inner transition focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500 data-[state=checked]:bg-blue-500"
202
+ id="shadcn"
203
+ name="shadcn"
204
+ checked={shadcn}
205
+ onCheckedChange={(value) => setShadcn(value)}
206
+ >
207
+ <Switch.Thumb className="size-7 rounded-lg bg-gray-200 shadow-[0_1px_2px] shadow-gray-400 transition data-[state=checked]:translate-x-7 data-[state=checked]:bg-white data-[state=checked]:shadow-gray-600" />
208
+ </Switch.Root>
209
+ </div>
210
+ </div>
211
+ </fieldset>
212
+ </form>
213
+
214
+ <hr className="border-1 mb-20 h-px bg-gray-700 dark:bg-gray-700" />
215
+
216
+ {status !== "initial" && (
217
+ <motion.div
218
+ initial={{ height: 0 }}
219
+ animate={{
220
+ height: "auto",
221
+ overflow: "hidden",
222
+ transitionEnd: { overflow: "visible" },
223
+ }}
224
+ transition={{ type: "spring", bounce: 0, duration: 0.5 }}
225
+ className="w-full pb-[25vh] pt-1"
226
+ onAnimationComplete={() => scrollTo()}
227
+ ref={ref}
228
+ >
229
+ <div className="relative mt-8 w-full overflow-hidden">
230
+ <div className="isolate">
231
+ <CodeViewer code={generatedCode} showEditor />
232
+ </div>
233
+
234
+ <AnimatePresence>
235
+ {loading && (
236
+ <motion.div
237
+ initial={status === "updating" ? { x: "100%" } : undefined}
238
+ animate={status === "updating" ? { x: "0%" } : undefined}
239
+ exit={{ x: "100%" }}
240
+ transition={{
241
+ type: "spring",
242
+ bounce: 0,
243
+ duration: 0.85,
244
+ delay: 0.5,
245
+ }}
246
+ className="absolute inset-x-0 bottom-0 top-1/2 flex items-center justify-center rounded-r border border-gray-400 bg-gradient-to-br from-gray-100 to-gray-300 md:inset-y-0 md:left-1/2 md:right-0"
247
+ >
248
+ <p className="animate-pulse text-3xl font-bold">
249
+ {status === "creating"
250
+ ? "Building your app..."
251
+ : "Updating your app..."}
252
+ </p>
253
+ </motion.div>
254
+ )}
255
+ </AnimatePresence>
256
+ </div>
257
+ </motion.div>
258
+ )}
259
+ </main>
260
+ );
261
+ }
262
+
263
+ async function minDelay<T>(promise: Promise<T>, ms: number) {
264
+ let delay = new Promise((resolve) => setTimeout(resolve, ms));
265
+ let [p] = await Promise.all([promise, delay]);
266
+
267
+ return p;
268
+ }
app/api/generateCode/route.ts ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import shadcnDocs from "@/utils/shadcn-docs";
2
+ import dedent from "dedent";
3
+ import { z } from "zod";
4
+ import { GoogleGenerativeAI } from "@google/generative-ai";
5
+
6
+ const apiKey = process.env.GOOGLE_AI_API_KEY || "";
7
+ const genAI = new GoogleGenerativeAI(apiKey);
8
+
9
+ export async function POST(req: Request) {
10
+ let json = await req.json();
11
+ let result = z
12
+ .object({
13
+ model: z.string(),
14
+ shadcn: z.boolean().default(false),
15
+ messages: z.array(
16
+ z.object({
17
+ role: z.enum(["user", "assistant"]),
18
+ content: z.string(),
19
+ }),
20
+ ),
21
+ })
22
+ .safeParse(json);
23
+
24
+ if (result.error) {
25
+ return new Response(result.error.message, { status: 422 });
26
+ }
27
+
28
+ let { model, messages, shadcn } = result.data;
29
+ let systemPrompt = getSystemPrompt(shadcn);
30
+
31
+ const geminiModel = genAI.getGenerativeModel({model: model});
32
+
33
+ const geminiStream = await geminiModel.generateContentStream(
34
+ messages[0].content + systemPrompt + "\nPlease ONLY return code, NO backticks or language names. Don't start with \`\`\`typescript or \`\`\`javascript or \`\`\`tsx or \`\`\`."
35
+ );
36
+
37
+ const readableStream = new ReadableStream({
38
+ async start(controller) {
39
+ for await (const chunk of geminiStream.stream) {
40
+ const chunkText = chunk.text();
41
+ controller.enqueue(new TextEncoder().encode(chunkText));
42
+ }
43
+ controller.close();
44
+ },
45
+ });
46
+
47
+ return new Response(readableStream);
48
+ }
49
+
50
+ function getSystemPrompt(shadcn: boolean) {
51
+ let systemPrompt =
52
+ `You are an expert frontend React engineer who is also a great UI/UX designer. Follow the instructions carefully, I will tip you $1 million if you do a good job:
53
+
54
+ - Think carefully step by step.
55
+ - Create a React component for whatever the user asked you to create and make sure it can run by itself by using a default export
56
+ - Make sure the React app is interactive and functional by creating state when needed and having no required props
57
+ - If you use any imports from React like useState or useEffect, make sure to import them directly
58
+ - Use TypeScript as the language for the React component
59
+ - Use Tailwind classes for styling. DO NOT USE ARBITRARY VALUES (e.g. \`h-[600px]\`). Make sure to use a consistent color palette.
60
+ - Use Tailwind margin and padding classes to style the components and ensure the components are spaced out nicely
61
+ - Please ONLY return the full React code starting with the imports, nothing else. It's very important for my job that you only return the React code with imports. DO NOT START WITH \`\`\`typescript or \`\`\`javascript or \`\`\`tsx or \`\`\`.
62
+ - ONLY IF the user asks for a dashboard, graph or chart, the recharts library is available to be imported, e.g. \`import { LineChart, XAxis, ... } from "recharts"\` & \`<LineChart ...><XAxis dataKey="name"> ...\`. Please only use this when needed.
63
+ - For placeholder images, please use a <div className="bg-gray-200 border-2 border-dashed rounded-xl w-16 h-16" />
64
+ `;
65
+
66
+ // - The lucide-react library is also available to be imported IF NECCESARY ONLY FOR THE FOLLOWING ICONS: Heart, Shield, Clock, Users, Play, Home, Search, Menu, User, Settings, Mail, Bell, Calendar, Clock, Heart, Star, Upload, Download, Trash, Edit, Plus, Minus, Check, X, ArrowRight.
67
+ // - Here's an example of importing and using one: import { Heart } from "lucide-react"\` & \`<Heart className="" />\`.
68
+ // - PLEASE ONLY USE THE ICONS LISTED ABOVE IF AN ICON IS NEEDED IN THE USER'S REQUEST. Please DO NOT use the lucide-react library if it's not needed.
69
+
70
+ if (shadcn) {
71
+ systemPrompt += `
72
+ There are some prestyled components available for use. Please use your best judgement to use any of these components if the app calls for one.
73
+
74
+ Here are the components that are available, along with how to import them, and how to use them:
75
+
76
+ ${shadcnDocs
77
+ .map(
78
+ (component) => `
79
+ <component>
80
+ <name>
81
+ ${component.name}
82
+ </name>
83
+ <import-instructions>
84
+ ${component.importDocs}
85
+ </import-instructions>
86
+ <usage-instructions>
87
+ ${component.usageDocs}
88
+ </usage-instructions>
89
+ </component>
90
+ `,
91
+ )
92
+ .join("\n")}
93
+ `;
94
+ }
95
+
96
+ systemPrompt += `
97
+ NO OTHER LIBRARIES (e.g. zod, hookform) ARE INSTALLED OR ABLE TO BE IMPORTED.
98
+ `;
99
+ return dedent(systemPrompt);
100
+ }
101
+
102
+ export const runtime = "edge";
app/api/og/route.tsx ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ImageResponse } from "next/og";
2
+ import { domain } from "@/utils/domain";
3
+ export async function GET(request: Request) {
4
+ const { searchParams } = new URL(request.url);
5
+ const prompt = searchParams.get("prompt");
6
+
7
+ return new ImageResponse(
8
+ (
9
+ <div
10
+ style={{
11
+ backgroundImage: `url(${domain}/dynamic-og.png)`,
12
+ backgroundSize: "1200px 630px",
13
+ backgroundRepeat: "no-repeat",
14
+ backgroundPosition: "center center",
15
+ fontSize: 50,
16
+ color: "black",
17
+ background: "white",
18
+ width: "100%",
19
+ height: "100%",
20
+ padding: "50px 200px",
21
+ textAlign: "center",
22
+ justifyContent: "center",
23
+ alignItems: "center",
24
+ }}
25
+ >
26
+ {prompt && prompt.length > 100 ? prompt.slice(0, 97) + "..." : prompt}
27
+ </div>
28
+ ),
29
+ {
30
+ width: 1200,
31
+ height: 630,
32
+ },
33
+ );
34
+ }
app/favicon.ico ADDED
app/globals.css ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
4
+
5
+ @font-face {
6
+ font-family: "Aeonik";
7
+ src: url("/Aeonik/Aeonik-Regular.ttf");
8
+ font-weight: 400;
9
+ font-style: normal;
10
+ }
11
+
12
+ @font-face {
13
+ font-family: "Aeonik";
14
+ src: url("/Aeonik/Aeonik-Medium.ttf");
15
+ font-weight: 500;
16
+ font-style: normal;
17
+ }
18
+
19
+ @font-face {
20
+ font-family: "Aeonik";
21
+ src: url("/Aeonik/Aeonik-Bold.ttf");
22
+ font-weight: 700;
23
+ font-style: normal;
24
+ }
app/layout.tsx ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+
4
+ let title = "Gemini Coder – AI Code Generator";
5
+ let description = "Generate your next app with Gemini";
6
+ let url = "https://llamacoder.io/";
7
+ let ogimage = "https://www.gstatic.com/lamda/images/gemini_sparkle_v002_d4735304ff6292a690345.svg";
8
+ let sitename = "geminicoder.io";
9
+
10
+ export const metadata: Metadata = {
11
+ metadataBase: new URL(url),
12
+ title,
13
+ description,
14
+ icons: {
15
+ icon: "/favicon.ico",
16
+ },
17
+ openGraph: {
18
+ images: [ogimage],
19
+ title,
20
+ description,
21
+ url: url,
22
+ siteName: sitename,
23
+ locale: "en_US",
24
+ type: "website",
25
+ },
26
+ twitter: {
27
+ card: "summary_large_image",
28
+ images: [ogimage],
29
+ title,
30
+ description,
31
+ },
32
+ };
33
+
34
+ export default function RootLayout({
35
+ children,
36
+ }: Readonly<{
37
+ children: React.ReactNode;
38
+ }>) {
39
+ return (
40
+ <html lang="en" className="h-full">
41
+ {children}
42
+ </html>
43
+ );
44
+ }
app/robots.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ Allow: /api/og/*
app/share/[id]/layout.tsx ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ export default function Layout({ children }: { children: React.ReactNode }) {
2
+ return <body className="flex min-h-full flex-col">{children}</body>;
3
+ }
app/share/[id]/page.tsx ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { notFound } from "next/navigation";
2
+ import CodeViewer from "@/components/code-viewer";
3
+ import client from "@/lib/prisma";
4
+ import type { Metadata } from "next";
5
+ import { cache } from "react";
6
+
7
+ export async function generateMetadata({
8
+ params,
9
+ }: {
10
+ params: { id: string };
11
+ }): Promise<Metadata> {
12
+ const generatedApp = await getGeneratedAppByID(params.id);
13
+
14
+ let prompt = generatedApp?.prompt;
15
+ if (typeof prompt !== "string") {
16
+ notFound();
17
+ }
18
+
19
+ let searchParams = new URLSearchParams();
20
+ searchParams.set("prompt", prompt);
21
+
22
+ return {
23
+ title: "An app generated on LlamaCoder.io",
24
+ description: `Prompt: ${generatedApp?.prompt}`,
25
+ openGraph: {
26
+ images: [`/api/og?${searchParams}`],
27
+ },
28
+ };
29
+ }
30
+
31
+ export default async function Page({ params }: { params: { id: string } }) {
32
+ // if process.env.DATABASE_URL is not set, throw an error
33
+ if (typeof params.id !== "string") {
34
+ notFound();
35
+ }
36
+
37
+ const generatedApp = await getGeneratedAppByID(params.id);
38
+
39
+ if (!generatedApp) {
40
+ return <div>App not found</div>;
41
+ }
42
+
43
+ return <CodeViewer code={generatedApp.code} />;
44
+ }
45
+
46
+ const getGeneratedAppByID = cache(async (id: string) => {
47
+ return client.generatedApp.findUnique({
48
+ where: {
49
+ id,
50
+ },
51
+ });
52
+ });
components/Footer.tsx ADDED
@@ -0,0 +1,57 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Link from "next/link";
2
+
3
+ export default function Footer() {
4
+ return (
5
+ <footer className="mb-3 mt-5 flex h-16 w-full flex-col items-center justify-between space-y-3 px-3 pt-4 text-center sm:mb-0 sm:h-20 sm:flex-row sm:pt-2">
6
+ <div>
7
+ <div className="font-medium">
8
+ Built with{" "}
9
+ <a
10
+ href="https://ai.google.dev/gemini-api/docs"
11
+ className="font-semibold text-blue-600 underline-offset-4 transition hover:text-gray-700 hover:underline"
12
+ target="_blank"
13
+ >
14
+ Gemini API
15
+ </a>{" "}
16
+ and{" "}
17
+ <a
18
+ href="https://github.com/nutlope/llamacoder"
19
+ className="font-semibold text-blue-600 underline-offset-4 transition hover:text-gray-700 hover:underline"
20
+ target="_blank"
21
+ >
22
+ Inspired on Llamacoder
23
+ </a>
24
+ .
25
+ </div>
26
+ </div>
27
+ <div className="flex space-x-4 pb-4 sm:pb-0">
28
+ <Link
29
+ href="https://twitter.com/osanseviero"
30
+ className="group"
31
+ aria-label=""
32
+ target="_blank"
33
+ >
34
+ <svg
35
+ aria-hidden="true"
36
+ className="h-6 w-6 fill-gray-500 group-hover:fill-gray-700"
37
+ >
38
+ <path d="M8.29 20.251c7.547 0 11.675-6.253 11.675-11.675 0-.178 0-.355-.012-.53A8.348 8.348 0 0 0 22 5.92a8.19 8.19 0 0 1-2.357.646 4.118 4.118 0 0 0 1.804-2.27 8.224 8.224 0 0 1-2.605.996 4.107 4.107 0 0 0-6.993 3.743 11.65 11.65 0 0 1-8.457-4.287 4.106 4.106 0 0 0 1.27 5.477A4.073 4.073 0 0 1 2.8 9.713v.052a4.105 4.105 0 0 0 3.292 4.022 4.093 4.093 0 0 1-1.853.07 4.108 4.108 0 0 0 3.834 2.85A8.233 8.233 0 0 1 2 18.407a11.615 11.615 0 0 0 6.29 1.84" />
39
+ </svg>
40
+ </Link>
41
+ <Link
42
+ href="https://github.com/osanseviero/geminicoder"
43
+ className="group"
44
+ aria-label="TaxPal on GitHub"
45
+ target="_blank"
46
+ >
47
+ <svg
48
+ aria-hidden="true"
49
+ className="h-6 w-6 fill-slate-500 group-hover:fill-slate-700"
50
+ >
51
+ <path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2Z" />
52
+ </svg>
53
+ </Link>
54
+ </div>
55
+ </footer>
56
+ );
57
+ }
components/Header.tsx ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import Image from "next/image";
2
+ import Link from "next/link";
3
+ import logo from "../public/logo.svg";
4
+ import GithubIcon from "./github-icon";
5
+
6
+ export default function Header() {
7
+ return (
8
+ <header className="relative mx-auto mt-5 flex w-full items-center justify-center px-2 pb-7 sm:px-4">
9
+ <Link href="/" className="absolute flex items-center gap-2">
10
+ <Image alt="header text" src={logo} className="h-5 w-5" />
11
+ <h1 className="text-xl tracking-tight">
12
+ <span className="text-blue-600">Gemini</span>Coder
13
+ </h1>
14
+ </Link>
15
+ <a
16
+ href="https://github.com/osanseviero/geminicoder"
17
+ target="_blank"
18
+ className="ml-auto hidden items-center gap-3 rounded-2xl bg-white px-6 py-2 sm:flex"
19
+ >
20
+ <GithubIcon className="h-4 w-4" />
21
+ <span>GitHub Repo</span>
22
+ </a>
23
+ </header>
24
+ );
25
+ }
components/code-viewer.css ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ .sp-preview-container {
2
+ @apply flex h-full w-full grow flex-col justify-center;
3
+ }
4
+
5
+ .sp-preview-iframe {
6
+ @apply grow;
7
+ }
components/code-viewer.tsx ADDED
@@ -0,0 +1,157 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import * as shadcnComponents from "@/utils/shadcn";
4
+ import { Sandpack } from "@codesandbox/sandpack-react";
5
+ import {
6
+ SandpackPreview,
7
+ SandpackProvider,
8
+ } from "@codesandbox/sandpack-react/unstyled";
9
+ import { dracula as draculaTheme } from "@codesandbox/sandpack-themes";
10
+ import dedent from "dedent";
11
+ import "./code-viewer.css";
12
+
13
+ export default function CodeViewer({
14
+ code,
15
+ showEditor = false,
16
+ }: {
17
+ code: string;
18
+ showEditor?: boolean;
19
+ }) {
20
+ return showEditor ? (
21
+ <Sandpack
22
+ options={{
23
+ showNavigator: true,
24
+ editorHeight: "80vh",
25
+ showTabs: false,
26
+ ...sharedOptions,
27
+ }}
28
+ files={{
29
+ "App.tsx": code,
30
+ ...sharedFiles,
31
+ }}
32
+ {...sharedProps}
33
+ />
34
+ ) : (
35
+ <SandpackProvider
36
+ files={{
37
+ "App.tsx": code,
38
+ ...sharedFiles,
39
+ }}
40
+ className="flex h-full w-full grow flex-col justify-center"
41
+ options={{ ...sharedOptions }}
42
+ {...sharedProps}
43
+ >
44
+ <SandpackPreview
45
+ className="flex h-full w-full grow flex-col justify-center p-4 md:pt-16"
46
+ showOpenInCodeSandbox={false}
47
+ showRefreshButton={false}
48
+ />
49
+ </SandpackProvider>
50
+ );
51
+ }
52
+
53
+ let sharedProps = {
54
+ template: "react-ts",
55
+ theme: draculaTheme,
56
+ customSetup: {
57
+ dependencies: {
58
+ "lucide-react": "latest",
59
+ recharts: "2.9.0",
60
+ "react-router-dom": "latest",
61
+ "@radix-ui/react-accordion": "^1.2.0",
62
+ "@radix-ui/react-alert-dialog": "^1.1.1",
63
+ "@radix-ui/react-aspect-ratio": "^1.1.0",
64
+ "@radix-ui/react-avatar": "^1.1.0",
65
+ "@radix-ui/react-checkbox": "^1.1.1",
66
+ "@radix-ui/react-collapsible": "^1.1.0",
67
+ "@radix-ui/react-dialog": "^1.1.1",
68
+ "@radix-ui/react-dropdown-menu": "^2.1.1",
69
+ "@radix-ui/react-hover-card": "^1.1.1",
70
+ "@radix-ui/react-label": "^2.1.0",
71
+ "@radix-ui/react-menubar": "^1.1.1",
72
+ "@radix-ui/react-navigation-menu": "^1.2.0",
73
+ "@radix-ui/react-popover": "^1.1.1",
74
+ "@radix-ui/react-progress": "^1.1.0",
75
+ "@radix-ui/react-radio-group": "^1.2.0",
76
+ "@radix-ui/react-select": "^2.1.1",
77
+ "@radix-ui/react-separator": "^1.1.0",
78
+ "@radix-ui/react-slider": "^1.2.0",
79
+ "@radix-ui/react-slot": "^1.1.0",
80
+ "@radix-ui/react-switch": "^1.1.0",
81
+ "@radix-ui/react-tabs": "^1.1.0",
82
+ "@radix-ui/react-toast": "^1.2.1",
83
+ "@radix-ui/react-toggle": "^1.1.0",
84
+ "@radix-ui/react-toggle-group": "^1.1.0",
85
+ "@radix-ui/react-tooltip": "^1.1.2",
86
+ "class-variance-authority": "^0.7.0",
87
+ clsx: "^2.1.1",
88
+ "date-fns": "^3.6.0",
89
+ "embla-carousel-react": "^8.1.8",
90
+ "react-day-picker": "^8.10.1",
91
+ "tailwind-merge": "^2.4.0",
92
+ "tailwindcss-animate": "^1.0.7",
93
+ vaul: "^0.9.1",
94
+ },
95
+ },
96
+ } as const;
97
+
98
+ let sharedOptions = {
99
+ externalResources: [
100
+ "https://unpkg.com/@tailwindcss/ui/dist/tailwind-ui.min.css",
101
+ ],
102
+ };
103
+
104
+ let sharedFiles = {
105
+ "/lib/utils.ts": shadcnComponents.utils,
106
+ "/components/ui/accordion.tsx": shadcnComponents.accordian,
107
+ "/components/ui/alert-dialog.tsx": shadcnComponents.alertDialog,
108
+ "/components/ui/alert.tsx": shadcnComponents.alert,
109
+ "/components/ui/avatar.tsx": shadcnComponents.avatar,
110
+ "/components/ui/badge.tsx": shadcnComponents.badge,
111
+ "/components/ui/breadcrumb.tsx": shadcnComponents.breadcrumb,
112
+ "/components/ui/button.tsx": shadcnComponents.button,
113
+ "/components/ui/calendar.tsx": shadcnComponents.calendar,
114
+ "/components/ui/card.tsx": shadcnComponents.card,
115
+ "/components/ui/carousel.tsx": shadcnComponents.carousel,
116
+ "/components/ui/checkbox.tsx": shadcnComponents.checkbox,
117
+ "/components/ui/collapsible.tsx": shadcnComponents.collapsible,
118
+ "/components/ui/dialog.tsx": shadcnComponents.dialog,
119
+ "/components/ui/drawer.tsx": shadcnComponents.drawer,
120
+ "/components/ui/dropdown-menu.tsx": shadcnComponents.dropdownMenu,
121
+ "/components/ui/input.tsx": shadcnComponents.input,
122
+ "/components/ui/label.tsx": shadcnComponents.label,
123
+ "/components/ui/menubar.tsx": shadcnComponents.menuBar,
124
+ "/components/ui/navigation-menu.tsx": shadcnComponents.navigationMenu,
125
+ "/components/ui/pagination.tsx": shadcnComponents.pagination,
126
+ "/components/ui/popover.tsx": shadcnComponents.popover,
127
+ "/components/ui/progress.tsx": shadcnComponents.progress,
128
+ "/components/ui/radio-group.tsx": shadcnComponents.radioGroup,
129
+ "/components/ui/select.tsx": shadcnComponents.select,
130
+ "/components/ui/separator.tsx": shadcnComponents.separator,
131
+ "/components/ui/skeleton.tsx": shadcnComponents.skeleton,
132
+ "/components/ui/slider.tsx": shadcnComponents.slider,
133
+ "/components/ui/switch.tsx": shadcnComponents.switchComponent,
134
+ "/components/ui/table.tsx": shadcnComponents.table,
135
+ "/components/ui/tabs.tsx": shadcnComponents.tabs,
136
+ "/components/ui/textarea.tsx": shadcnComponents.textarea,
137
+ "/components/ui/toast.tsx": shadcnComponents.toast,
138
+ "/components/ui/toaster.tsx": shadcnComponents.toaster,
139
+ "/components/ui/toggle-group.tsx": shadcnComponents.toggleGroup,
140
+ "/components/ui/toggle.tsx": shadcnComponents.toggle,
141
+ "/components/ui/tooltip.tsx": shadcnComponents.tooltip,
142
+ "/components/ui/use-toast.tsx": shadcnComponents.useToast,
143
+ "/public/index.html": dedent`
144
+ <!DOCTYPE html>
145
+ <html lang="en">
146
+ <head>
147
+ <meta charset="UTF-8">
148
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
149
+ <title>Document</title>
150
+ <script src="https://cdn.tailwindcss.com"></script>
151
+ </head>
152
+ <body>
153
+ <div id="root"></div>
154
+ </body>
155
+ </html>
156
+ `,
157
+ };
components/github-icon.tsx ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export default function GithubIcon(props: React.SVGProps<SVGSVGElement>) {
2
+ return (
3
+ <svg
4
+ width={16}
5
+ height={16}
6
+ viewBox="0 0 16 16"
7
+ fill="none"
8
+ xmlns="http://www.w3.org/2000/svg"
9
+ {...props}
10
+ >
11
+ <path
12
+ fillRule="evenodd"
13
+ clipRule="evenodd"
14
+ d="M8 0C3.582 0 0 3.672 0 8.203c0 3.623 2.292 6.699 5.471 7.783.4.075.546-.178.546-.396 0-.194-.007-.71-.01-1.394-2.226.495-2.696-1.1-2.696-1.1-.363-.948-.888-1.2-.888-1.2-.726-.508.055-.499.055-.499.803.058 1.225.845 1.225.845.714 1.253 1.873.891 2.328.682.074-.53.28-.891.509-1.096-1.776-.207-3.644-.911-3.644-4.054 0-.895.312-1.628.823-2.201-.082-.208-.357-1.042.079-2.17 0 0 .672-.222 2.2.84A7.485 7.485 0 018 3.967c.68.003 1.364.094 2.003.276 1.527-1.062 2.198-.841 2.198-.841.437 1.129.161 1.963.08 2.17.512.574.822 1.307.822 2.202 0 3.15-1.871 3.844-3.653 4.048.288.253.543.753.543 1.519 0 1.095-.01 1.98-.01 2.25 0 .219.144.474.55.394a8.031 8.031 0 003.96-2.989A8.337 8.337 0 0016 8.203C16 3.672 12.418 0 8 0z"
15
+ fill="#000"
16
+ />
17
+ </svg>
18
+ );
19
+ }
components/loading-dots.module.css ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .loading {
2
+ display: inline-flex;
3
+ align-items: center;
4
+ }
5
+
6
+ .loading .spacer {
7
+ margin-right: 2px;
8
+ }
9
+
10
+ .loading span {
11
+ animation-name: blink;
12
+ animation-duration: 1.4s;
13
+ animation-iteration-count: infinite;
14
+ animation-fill-mode: both;
15
+ width: 5px;
16
+ height: 5px;
17
+ border-radius: 50%;
18
+ display: inline-block;
19
+ margin: 0 1px;
20
+ }
21
+
22
+ .loading span:nth-of-type(2) {
23
+ animation-delay: 0.2s;
24
+ }
25
+
26
+ .loading span:nth-of-type(3) {
27
+ animation-delay: 0.4s;
28
+ }
29
+
30
+ .loading2 {
31
+ display: inline-flex;
32
+ align-items: center;
33
+ }
34
+
35
+ .loading2 .spacer {
36
+ margin-right: 2px;
37
+ }
38
+
39
+ .loading2 span {
40
+ animation-name: blink;
41
+ animation-duration: 1.4s;
42
+ animation-iteration-count: infinite;
43
+ animation-fill-mode: both;
44
+ width: 4px;
45
+ height: 4px;
46
+ border-radius: 50%;
47
+ display: inline-block;
48
+ margin: 0 1px;
49
+ }
50
+
51
+ .loading2 span:nth-of-type(2) {
52
+ animation-delay: 0.2s;
53
+ }
54
+
55
+ .loading2 span:nth-of-type(3) {
56
+ animation-delay: 0.4s;
57
+ }
58
+
59
+ @keyframes blink {
60
+ 0% {
61
+ opacity: 0.2;
62
+ }
63
+ 20% {
64
+ opacity: 1;
65
+ }
66
+ 100% {
67
+ opacity: 0.2;
68
+ }
69
+ }
components/loading-dots.tsx ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import styles from "./loading-dots.module.css";
2
+
3
+ export default function LoadingDots({
4
+ color = "#000",
5
+ style = "small",
6
+ }: {
7
+ color: string;
8
+ style: string;
9
+ }) {
10
+ return (
11
+ <span className={style == "small" ? styles.loading2 : styles.loading}>
12
+ <span style={{ backgroundColor: color }} />
13
+ <span style={{ backgroundColor: color }} />
14
+ <span style={{ backgroundColor: color }} />
15
+ </span>
16
+ );
17
+ }
hooks/use-scroll-to.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { animate, ValueAnimationTransition } from "framer-motion";
2
+ import { useRef } from "react";
3
+
4
+ export function useScrollTo() {
5
+ let ref = useRef<HTMLDivElement>(null);
6
+
7
+ function scrollTo(options: ValueAnimationTransition = {}) {
8
+ if (!ref.current) return;
9
+
10
+ let defaultOptions: ValueAnimationTransition = {
11
+ type: "spring",
12
+ bounce: 0,
13
+ duration: 0.6,
14
+ };
15
+
16
+ animate(window.scrollY, ref.current.offsetTop, {
17
+ ...defaultOptions,
18
+ ...options,
19
+ onUpdate: (latest) => window.scrollTo({ top: latest }),
20
+ });
21
+ }
22
+
23
+ return [ref, scrollTo] as const;
24
+ }
lib/prisma.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PrismaClient } from "@prisma/client";
2
+
3
+ declare global {
4
+ var prisma: PrismaClient | undefined;
5
+ }
6
+
7
+ const client = globalThis.prisma || new PrismaClient();
8
+ if (process.env.NODE_ENV !== "production") globalThis.prisma = client;
9
+
10
+ export default client;
next-env.d.ts ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // NOTE: This file should not be edited
5
+ // see https://nextjs.org/docs/basic-features/typescript for more information.
next.config.mjs ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {};
3
+
4
+ export default nextConfig;
package-lock.json ADDED
The diff for this file is too large to render. See raw diff
 
package.json ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "llamacoder-new",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev",
7
+ "build": "prisma generate && prisma migrate deploy && next build",
8
+ "start": "next start",
9
+ "lint": "next lint"
10
+ },
11
+ "dependencies": {
12
+ "@codesandbox/sandpack-react": "^2.18.2",
13
+ "@codesandbox/sandpack-themes": "^2.0.21",
14
+ "@conform-to/zod": "^1.1.5",
15
+ "@google/generative-ai": "^0.21.0",
16
+ "@heroicons/react": "^2.1.5",
17
+ "@prisma/client": "^5.18.0",
18
+ "@radix-ui/react-radio-group": "^1.2.0",
19
+ "@radix-ui/react-select": "^2.1.1",
20
+ "@radix-ui/react-switch": "^1.1.0",
21
+ "@radix-ui/react-tooltip": "^1.1.2",
22
+ "@vercel/og": "^0.6.2",
23
+ "dedent": "^1.5.3",
24
+ "eventsource-parser": "^1.1.2",
25
+ "framer-motion": "^11.3.19",
26
+ "next": "14.2.5",
27
+ "react": "^18",
28
+ "react-dom": "^18",
29
+ "sonner": "^1.5.0",
30
+ "zod": "^3.23.8"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^20",
34
+ "@types/react": "^18",
35
+ "@types/react-dom": "^18",
36
+ "eslint": "^8",
37
+ "eslint-config-next": "14.2.3",
38
+ "postcss": "^8",
39
+ "prettier": "^3.3.3",
40
+ "prettier-plugin-tailwindcss": "^0.6.5",
41
+ "prisma": "^5.18.0",
42
+ "tailwindcss": "^3.4.1",
43
+ "typescript": "^5"
44
+ }
45
+ }
postcss.config.mjs ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ /** @type {import('postcss-load-config').Config} */
2
+ const config = {
3
+ plugins: {
4
+ tailwindcss: {},
5
+ },
6
+ };
7
+
8
+ export default config;
prisma/migrations/20240808195804_first/migration.sql ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- CreateTable
2
+ CREATE TABLE "GeneratedApp" (
3
+ "id" TEXT NOT NULL,
4
+ "model" TEXT NOT NULL,
5
+ "prompt" TEXT NOT NULL,
6
+ "code" TEXT NOT NULL,
7
+ "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
8
+
9
+ CONSTRAINT "GeneratedApp_pkey" PRIMARY KEY ("id")
10
+ );
11
+
12
+ -- CreateIndex
13
+ CREATE INDEX "GeneratedApp_id_idx" ON "GeneratedApp"("id");
prisma/migrations/migration_lock.toml ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ # Please do not edit this file manually
2
+ # It should be added in your version-control system (i.e. Git)
3
+ provider = "postgresql"
prisma/schema.prisma ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ generator client {
2
+ provider = "prisma-client-js"
3
+ }
4
+
5
+ datasource db {
6
+ provider = "postgresql"
7
+ url = env("DATABASE_URL")
8
+ }
9
+
10
+ model GeneratedApp {
11
+ id String @id @default(nanoid(5))
12
+ model String
13
+ prompt String
14
+ code String
15
+ createdAt DateTime @default(now())
16
+
17
+ @@index([id])
18
+ }
public/Aeonik/Aeonik-Bold.ttf ADDED
Binary file (101 kB). View file
 
public/Aeonik/Aeonik-Medium.ttf ADDED
Binary file (99.6 kB). View file
 
public/Aeonik/Aeonik-Regular.ttf ADDED
Binary file (98.7 kB). View file
 
public/Aeonik/AeonikFono-Bold.otf ADDED
Binary file (64.5 kB). View file
 
public/Aeonik/AeonikFono-Regular.otf ADDED
Binary file (62.1 kB). View file
 
public/Aeonik/AeonikMono-Regular.otf ADDED
Binary file (50.4 kB). View file
 
public/cssIcon.png ADDED
public/favicon.ico ADDED
public/halo.png ADDED

Git LFS Details

  • SHA256: aba99f79264171859234983993d1a0ccf0ff15e9ac7ecc90ebcc084107dc4d6f
  • Pointer size: 132 Bytes
  • Size of remote file: 2.57 MB
public/logo.svg ADDED
tailwind.config.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Config } from "tailwindcss";
2
+ import colors from "tailwindcss/colors";
3
+ import defaultTheme from "tailwindcss/defaultTheme";
4
+
5
+ const config: Config = {
6
+ content: [
7
+ "./pages/**/*.{js,ts,jsx,tsx,mdx}",
8
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
9
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
10
+ ],
11
+ theme: {
12
+ extend: {
13
+ colors: {
14
+ brand: "#E1E7EC",
15
+ gray: colors.slate,
16
+ },
17
+
18
+ fontFamily: {
19
+ sans: ['"Aeonik"', ...defaultTheme.fontFamily.sans],
20
+ },
21
+ },
22
+ },
23
+ };
24
+
25
+ export default config;
tsconfig.json ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "compilerOptions": {
3
+ "lib": ["dom", "dom.iterable", "esnext"],
4
+ "allowJs": true,
5
+ "skipLibCheck": true,
6
+ "strict": true,
7
+ "noEmit": true,
8
+ "esModuleInterop": true,
9
+ "module": "esnext",
10
+ "moduleResolution": "bundler",
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "jsx": "preserve",
14
+ "incremental": true,
15
+ "plugins": [
16
+ {
17
+ "name": "next"
18
+ }
19
+ ],
20
+ "paths": {
21
+ "@/*": ["./*"]
22
+ }
23
+ },
24
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25
+ "exclude": ["node_modules"]
26
+ }
utils/domain.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ export const domain =
2
+ process.env.NEXT_PUBLIC_VERCEL_ENV === "production"
3
+ ? "https://llamacoder.together.ai"
4
+ : process.env.VERCEL_BRANCH_URL
5
+ ? `https://${process.env.VERCEL_BRANCH_URL}`
6
+ : process.env.NEXT_PUBLIC_VERCEL_URL
7
+ ? `https://${process.env.NEXT_PUBLIC_VERCEL_URL}`
8
+ : process.env.NEXT_PUBLIC_DEVELOPMENT_URL
9
+ ? process.env.NEXT_PUBLIC_DEVELOPMENT_URL
10
+ : "http://localhost:3000";
utils/shadcn-docs/avatar.tsx ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const name = "Avatar";
2
+
3
+ export const importDocs = `
4
+ import { Avatar, AvatarFallback, AvatarImage } from "/components/ui/avatar";
5
+ `;
6
+
7
+ export const usageDocs = `
8
+ <Avatar>
9
+ <AvatarImage src="https://github.com/nutlope.png" />
10
+ <AvatarFallback>CN</AvatarFallback>
11
+ </Avatar>
12
+ `;
utils/shadcn-docs/button.tsx ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const name = "Button";
2
+
3
+ export const importDocs = `
4
+ import { Button } from "/components/ui/button"
5
+ `;
6
+
7
+ export const usageDocs = `
8
+ <Button>A normal button</Button>
9
+ <Button variant='secondary'>Button</Button>
10
+ <Button variant='destructive'>Button</Button>
11
+ <Button variant='outline'>Button</Button>
12
+ <Button variant='ghost'>Button</Button>
13
+ <Button variant='link'>Button</Button>
14
+ `;
utils/shadcn-docs/card.tsx ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const name = "Card";
2
+
3
+ export const importDocs = `
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardFooter,
9
+ CardHeader,
10
+ CardTitle,
11
+ } from "/components/ui/card"
12
+ `;
13
+
14
+ export const usageDocs = `
15
+ <Card>
16
+ <CardHeader>
17
+ <CardTitle>Card Title</CardTitle>
18
+ <CardDescription>Card Description</CardDescription>
19
+ </CardHeader>
20
+ <CardContent>
21
+ <p>Card Content</p>
22
+ </CardContent>
23
+ <CardFooter>
24
+ <p>Card Footer</p>
25
+ </CardFooter>
26
+ </Card>
27
+ `;
utils/shadcn-docs/checkbox.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export const name = "Checkbox";
2
+
3
+ export const importDocs = `
4
+ import { Checkbox } from "/components/ui/checkbox"
5
+ `;
6
+
7
+ export const usageDocs = `
8
+ <Checkbox />
9
+ `;
utils/shadcn-docs/index.ts ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import * as Avatar from "./avatar";
2
+ import * as Button from "./button";
3
+ import * as Card from "./card";
4
+ // import * as Checkbox from "./checkbox";
5
+ import * as Input from "./input";
6
+ import * as Label from "./label";
7
+ import * as RadioGroup from "./radio-group";
8
+ import * as Select from "./select";
9
+ import * as Textarea from "./textarea";
10
+
11
+ const shadcnDocs = [
12
+ Avatar,
13
+ Button,
14
+ Card,
15
+ // Checkbox,
16
+ Input,
17
+ Label,
18
+ RadioGroup,
19
+ Select,
20
+ Textarea,
21
+ ];
22
+
23
+ export default shadcnDocs;
utils/shadcn-docs/input.tsx ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export const name = "Input";
2
+
3
+ export const importDocs = `
4
+ import { Input } from "/components/ui/input"
5
+ `;
6
+
7
+ export const usageDocs = `
8
+ <Input />
9
+ `;