Sayedev commited on
Commit
9ebfcd2
·
verified ·
1 Parent(s): a3b6162

Upload 59 files

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
.eslintrc.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "extends": "next/core-web-vitals"
3
+ }
.gitattributes ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ public/Aeonik/Aeonik-Bold.ttf filter=lfs diff=lfs merge=lfs -text
2
+ public/halo.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2
+
3
+ # dependencies
4
+ /node_modules
5
+ /.pnp
6
+ .pnp.js
7
+ .yarn/install-state.gz
8
+
9
+ # testing
10
+ /coverage
11
+
12
+ # next.js
13
+ /.next/
14
+ /out/
15
+
16
+ # production
17
+ /build
18
+
19
+ # misc
20
+ .DS_Store
21
+ *.pem
22
+
23
+ # debug
24
+ npm-debug.log*
25
+ yarn-debug.log*
26
+ yarn-error.log*
27
+
28
+ # local env files
29
+ .env*.local
30
+ .env
31
+
32
+ # vercel
33
+ .vercel
34
+
35
+ # typescript
36
+ *.tsbuildinfo
37
+ next-env.d.ts
.prettierrc ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ {
2
+ "plugins": ["prettier-plugin-tailwindcss"]
3
+ }
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 ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <h1 align="center">InstantCoder</h1>
2
+ <p align="center">
3
+ Generate small apps with one prompt. Powered by the Gemini API.
4
+ </p>
5
+
6
+ Try it in https://huggingface.co/spaces/osanseviero/InstantCoder
7
+
8
+ This project is fully based on [llamacoder](https://github.com/Nutlope/llamacoder). Please follow [Nutlope](https://github.com/Nutlope) and give them a star.
9
+
10
+ ## Tech stack
11
+
12
+ - [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
13
+ - [Sandpack](https://sandpack.codesandbox.io/) for the code sandbox
14
+ - Next.js app router with Tailwind
15
+
16
+ You can also experiment with Gemini in [Google AI Studio](https://aistudio.google.com/).
17
+
18
+ ## Cloning & running
19
+
20
+ 1. Clone the repo: `git clone https://github.com/osanseviero/GemCoder`
21
+ 2. Create a `.env` file and add your [Google AI Studio API key](https://aistudio.google.com/app/apikey): `GOOGLE_AI_API_KEY=`
22
+ 3. Run `npm install` and `npm run dev` to install dependencies and run locally
23
+
24
+ **This is a personal project and not a Google official project**
app/(main)/layout.tsx ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ import ThemeToggle from "@/components/ThemeToggle";
6
+
7
+ export default function Layout({
8
+ children,
9
+ }: Readonly<{
10
+ children: React.ReactNode;
11
+ }>) {
12
+ return (
13
+ <body className="bg-brand dark:bg-dark antialiased dark:text-gray-100">
14
+ <div className="absolute inset-0 dark:bg-dark-radial" />
15
+ <div className="absolute inset-x-0 flex justify-center">
16
+ <Image
17
+ src={bgImg}
18
+ alt=""
19
+ className="w-full max-w-[1200px] mix-blend-screen dark:mix-blend-plus-lighter dark:opacity-10"
20
+ priority
21
+ />
22
+ </div>
23
+
24
+ <div className="isolate relative">
25
+ <div className="mx-auto flex min-h-screen max-w-7xl flex-col items-center justify-center py-2">
26
+ <div className="fixed right-4 top-4 z-50">
27
+ <ThemeToggle />
28
+ </div>
29
+ <Header />
30
+ {children}
31
+ <Footer />
32
+ </div>
33
+ </div>
34
+ </body>
35
+ );
36
+ }
app/(main)/page.tsx ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 [modification, setModification] = useState("");
39
+ let [generatedCode, setGeneratedCode] = useState("");
40
+ let [initialAppConfig, setInitialAppConfig] = useState({
41
+ model: "",
42
+ });
43
+ let [ref, scrollTo] = useScrollTo();
44
+ let [messages, setMessages] = useState<{ role: string; content: string }[]>(
45
+ [],
46
+ );
47
+
48
+ let loading = status === "creating" || status === "updating";
49
+
50
+ async function createApp(e: FormEvent<HTMLFormElement>) {
51
+ e.preventDefault();
52
+
53
+ if (status !== "initial") {
54
+ scrollTo({ delay: 0.5 });
55
+ }
56
+
57
+ setStatus("creating");
58
+ setGeneratedCode("");
59
+
60
+ let res = await fetch("/api/generateCode", {
61
+ method: "POST",
62
+ headers: {
63
+ "Content-Type": "application/json",
64
+ },
65
+ body: JSON.stringify({
66
+ model,
67
+ messages: [{ role: "user", content: prompt }],
68
+ }),
69
+ });
70
+
71
+ if (!res.ok) {
72
+ throw new Error(res.statusText);
73
+ }
74
+
75
+ if (!res.body) {
76
+ throw new Error("No response body");
77
+ }
78
+
79
+ const reader = res.body.getReader();
80
+ let receivedData = "";
81
+
82
+ while (true) {
83
+ const { done, value } = await reader.read();
84
+ if (done) {
85
+ break;
86
+ }
87
+ receivedData += new TextDecoder().decode(value);
88
+ const cleanedData = removeCodeFormatting(receivedData);
89
+ setGeneratedCode(cleanedData);
90
+ }
91
+
92
+ setMessages([{ role: "user", content: prompt }]);
93
+ setInitialAppConfig({ model });
94
+ setStatus("created");
95
+ }
96
+
97
+ useEffect(() => {
98
+ let el = document.querySelector(".cm-scroller");
99
+ if (el && loading) {
100
+ let end = el.scrollHeight - el.clientHeight;
101
+ el.scrollTo({ top: end });
102
+ }
103
+ }, [loading, generatedCode]);
104
+
105
+ return (
106
+ <main className="mt-12 flex w-full flex-1 flex-col items-center px-4 text-center sm:mt-1">
107
+ <a
108
+ 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)] dark:bg-[rgba(30,41,59,0.5)] dark:border-gray-700 px-7 py-5 shadow-[0px_1px_1px_0px_rgba(0,0,0,0.25)]"
109
+ href="https://ai.google.dev/gemini-api/docs"
110
+ target="_blank"
111
+ >
112
+ <span className="text-center">
113
+ Powered by <span className="font-medium">Gemini API</span>
114
+ </span>
115
+ </a>
116
+ <h1 className="my-6 max-w-3xl text-4xl font-bold text-gray-800 dark:text-white sm:text-6xl">
117
+ Turn your <span className="text-blue-600">idea</span>
118
+ <br /> into an <span className="text-blue-600">app</span>
119
+ </h1>
120
+
121
+ <form className="w-full max-w-xl" onSubmit={createApp}>
122
+ <fieldset disabled={loading} className="disabled:opacity-75">
123
+ <div className="relative mt-5">
124
+ <div className="absolute -inset-2 rounded-[32px] bg-gray-300/50 dark:bg-gray-800/50" />
125
+ <div className="relative flex rounded-3xl bg-white dark:bg-[#1E293B] shadow-sm">
126
+ <div className="relative flex flex-grow items-stretch focus-within:z-10">
127
+ <textarea
128
+ rows={3}
129
+ required
130
+ value={prompt}
131
+ onChange={(e) => setPrompt(e.target.value)}
132
+ name="prompt"
133
+ 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 dark:text-gray-100 dark:placeholder-gray-400"
134
+ placeholder="Build me a calculator app..."
135
+ />
136
+ </div>
137
+ <button
138
+ type="submit"
139
+ disabled={loading}
140
+ 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 dark:disabled:text-gray-400"
141
+ >
142
+ {status === "creating" ? (
143
+ <LoadingDots color="black" style="large" />
144
+ ) : (
145
+ <ArrowLongRightIcon className="-ml-0.5 size-6" />
146
+ )}
147
+ </button>
148
+ </div>
149
+ </div>
150
+ <div className="mt-6 flex flex-col justify-center gap-4 sm:flex-row sm:items-center sm:gap-8">
151
+ <div className="flex items-center justify-between gap-3 sm:justify-center">
152
+ <p className="text-gray-500 dark:text-gray-400 sm:text-xs">Model:</p>
153
+ <Select.Root
154
+ name="model"
155
+ disabled={loading}
156
+ value={model}
157
+ onValueChange={(value) => setModel(value)}
158
+ >
159
+ <Select.Trigger className="group flex w-60 max-w-xs items-center rounded-2xl border-[6px] border-gray-300 dark:border-gray-700 bg-white dark:bg-[#1E293B] px-4 py-2 text-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-blue-500">
160
+ <Select.Value />
161
+ <Select.Icon className="ml-auto">
162
+ <ChevronDownIcon className="size-6 text-gray-300 group-focus-visible:text-gray-500 group-enabled:group-hover:text-gray-500 dark:text-gray-600 dark:group-focus-visible:text-gray-400 dark:group-enabled:group-hover:text-gray-400" />
163
+ </Select.Icon>
164
+ </Select.Trigger>
165
+ <Select.Portal>
166
+ <Select.Content className="overflow-hidden rounded-md bg-white dark:bg-[#1E293B] shadow-lg">
167
+ <Select.Viewport className="p-2">
168
+ {models.map((model) => (
169
+ <Select.Item
170
+ key={model.value}
171
+ value={model.value}
172
+ className="flex cursor-pointer items-center rounded-md px-3 py-2 text-sm data-[highlighted]:bg-gray-100 dark:data-[highlighted]:bg-gray-800 data-[highlighted]:outline-none"
173
+ >
174
+ <Select.ItemText asChild>
175
+ <span className="inline-flex items-center gap-2 text-gray-500 dark:text-gray-400">
176
+ <div className="size-2 rounded-full bg-green-500" />
177
+ {model.label}
178
+ </span>
179
+ </Select.ItemText>
180
+ <Select.ItemIndicator className="ml-auto">
181
+ <CheckIcon className="size-5 text-blue-600" />
182
+ </Select.ItemIndicator>
183
+ </Select.Item>
184
+ ))}
185
+ </Select.Viewport>
186
+ <Select.ScrollDownButton />
187
+ <Select.Arrow />
188
+ </Select.Content>
189
+ </Select.Portal>
190
+ </Select.Root>
191
+ </div>
192
+ </div>
193
+ </fieldset>
194
+ </form>
195
+
196
+ <hr className="border-1 mb-20 h-px bg-gray-700 dark:bg-gray-700/30" />
197
+
198
+ {status !== "initial" && (
199
+ <motion.div
200
+ initial={{ height: 0 }}
201
+ animate={{
202
+ height: "auto",
203
+ overflow: "hidden",
204
+ transitionEnd: { overflow: "visible" },
205
+ }}
206
+ transition={{ type: "spring", bounce: 0, duration: 0.5 }}
207
+ className="w-full pb-[25vh] pt-1"
208
+ onAnimationComplete={() => scrollTo()}
209
+ ref={ref}
210
+ >
211
+ <div className="relative mt-8 w-full overflow-hidden">
212
+ <div className="isolate">
213
+ <CodeViewer code={generatedCode} showEditor />
214
+ </div>
215
+
216
+ <AnimatePresence>
217
+ {loading && (
218
+ <motion.div
219
+ initial={status === "updating" ? { x: "100%" } : undefined}
220
+ animate={status === "updating" ? { x: "0%" } : undefined}
221
+ exit={{ x: "100%" }}
222
+ transition={{
223
+ type: "spring",
224
+ bounce: 0,
225
+ duration: 0.85,
226
+ delay: 0.5,
227
+ }}
228
+ className="absolute inset-x-0 bottom-0 top-1/2 flex items-center justify-center rounded-r border border-gray-400 dark:border-gray-700 bg-gradient-to-br from-gray-100 to-gray-300 dark:from-[#1E293B] dark:to-gray-800 md:inset-y-0 md:left-1/2 md:right-0"
229
+ >
230
+ <p className="animate-pulse text-3xl font-bold dark:text-gray-100">
231
+ {status === "creating"
232
+ ? "Building your app..."
233
+ : "Updating your app..."}
234
+ </p>
235
+ </motion.div>
236
+ )}
237
+ </AnimatePresence>
238
+ </div>
239
+ </motion.div>
240
+ )}
241
+ </main>
242
+ );
243
+ }
244
+
245
+ async function minDelay<T>(promise: Promise<T>, ms: number) {
246
+ let delay = new Promise((resolve) => setTimeout(resolve, ms));
247
+ let [p] = await Promise.all([promise, delay]);
248
+
249
+ return p;
250
+ }
app/api/generateCode/route.ts ADDED
@@ -0,0 +1,73 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import dedent from "dedent";
2
+ import { z } from "zod";
3
+ import { GoogleGenerativeAI } from "@google/generative-ai";
4
+
5
+ const apiKey = process.env.GOOGLE_AI_API_KEY || "";
6
+ const genAI = new GoogleGenerativeAI(apiKey);
7
+
8
+ export async function POST(req: Request) {
9
+ let json = await req.json();
10
+ let result = z
11
+ .object({
12
+ model: z.string(),
13
+ messages: z.array(
14
+ z.object({
15
+ role: z.enum(["user", "assistant"]),
16
+ content: z.string(),
17
+ }),
18
+ ),
19
+ })
20
+ .safeParse(json);
21
+
22
+ if (result.error) {
23
+ return new Response(result.error.message, { status: 422 });
24
+ }
25
+
26
+ let { model, messages } = result.data;
27
+ let systemPrompt = getSystemPrompt();
28
+
29
+ const geminiModel = genAI.getGenerativeModel({model: model});
30
+
31
+ const geminiStream = await geminiModel.generateContentStream(
32
+ messages[0].content + systemPrompt + "\nPlease ONLY return code, NO backticks or language names. Don't start with \`\`\`typescript or \`\`\`javascript or \`\`\`tsx or \`\`\`."
33
+ );
34
+
35
+ console.log(messages[0].content + systemPrompt + "\nPlease ONLY return code, NO backticks or language names. Don't start with \`\`\`typescript or \`\`\`javascript or \`\`\`tsx or \`\`\`.")
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() {
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
+ systemPrompt += `
67
+ NO OTHER LIBRARIES (e.g. zod, hookform) ARE INSTALLED OR ABLE TO BE IMPORTED.
68
+ `;
69
+
70
+ return dedent(systemPrompt);
71
+ }
72
+
73
+ 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,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Metadata } from "next";
2
+ import "./globals.css";
3
+ import { ThemeProvider } from "@/components/ThemeProvider";
4
+
5
+ let title = "Gemini Coder – AI Code Generator";
6
+ let description = "Generate your next app with Gemini";
7
+ let url = "https://llamacoder.io/";
8
+ let ogimage = "https://www.gstatic.com/lamda/images/gemini_sparkle_v002_d4735304ff6292a690345.svg";
9
+ let sitename = "geminicoder.io";
10
+
11
+ export const metadata: Metadata = {
12
+ metadataBase: new URL(url),
13
+ title,
14
+ description,
15
+ icons: {
16
+ icon: "/favicon.ico",
17
+ },
18
+ openGraph: {
19
+ images: [ogimage],
20
+ title,
21
+ description,
22
+ url: url,
23
+ siteName: sitename,
24
+ locale: "en_US",
25
+ type: "website",
26
+ },
27
+ twitter: {
28
+ card: "summary_large_image",
29
+ images: [ogimage],
30
+ title,
31
+ description,
32
+ },
33
+ };
34
+
35
+ export default function RootLayout({
36
+ children,
37
+ }: Readonly<{
38
+ children: React.ReactNode;
39
+ }>) {
40
+ return (
41
+ <html lang="en" className="h-full">
42
+ <ThemeProvider>
43
+ {children}
44
+ </ThemeProvider>
45
+ </html>
46
+ );
47
+ }
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/DarkModeToggle.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { useDarkMode } from '@/hooks/useDarkMode';
4
+
5
+ export function DarkModeToggle() {
6
+ const { isDarkMode, toggleDarkMode } = useDarkMode();
7
+
8
+ return (
9
+ <button
10
+ onClick={toggleDarkMode}
11
+ className="p-2 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors"
12
+ aria-label="Toggle dark mode"
13
+ >
14
+ {isDarkMode ? (
15
+ <svg
16
+ xmlns="http://www.w3.org/2000/svg"
17
+ fill="none"
18
+ viewBox="0 0 24 24"
19
+ strokeWidth={1.5}
20
+ stroke="currentColor"
21
+ className="w-5 h-5"
22
+ >
23
+ <path
24
+ strokeLinecap="round"
25
+ strokeLinejoin="round"
26
+ d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
27
+ />
28
+ </svg>
29
+ ) : (
30
+ <svg
31
+ xmlns="http://www.w3.org/2000/svg"
32
+ fill="none"
33
+ viewBox="0 0 24 24"
34
+ strokeWidth={1.5}
35
+ stroke="currentColor"
36
+ className="w-5 h-5"
37
+ >
38
+ <path
39
+ strokeLinecap="round"
40
+ strokeLinejoin="round"
41
+ d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
42
+ />
43
+ </svg>
44
+ )}
45
+ </button>
46
+ );
47
+ }
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
+ . This is not an official Google product.
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 dark:bg-[#1E293B] dark:text-gray-100 px-6 py-2 sm:flex border border-gray-200 dark:border-gray-700"
19
+ >
20
+ <GithubIcon className="h-4 w-4 dark:text-gray-100" />
21
+ <span>GitHub Repo</span>
22
+ </a>
23
+ </header>
24
+ );
25
+ }
components/ThemeProvider.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { createContext, useContext, useEffect, useState } from "react";
4
+
5
+ type Theme = "light" | "dark";
6
+
7
+ type ThemeContextType = {
8
+ theme: Theme;
9
+ toggleTheme: () => void;
10
+ };
11
+
12
+ const ThemeContext = createContext<ThemeContextType | undefined>(undefined);
13
+
14
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
15
+ const [theme, setTheme] = useState<Theme>("light");
16
+
17
+ useEffect(() => {
18
+ const savedTheme = localStorage.getItem("theme") as Theme;
19
+ if (savedTheme) {
20
+ setTheme(savedTheme);
21
+ document.documentElement.classList.toggle("dark", savedTheme === "dark");
22
+ } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
23
+ setTheme("dark");
24
+ document.documentElement.classList.add("dark");
25
+ }
26
+ }, []);
27
+
28
+ const toggleTheme = () => {
29
+ const newTheme = theme === "light" ? "dark" : "light";
30
+ setTheme(newTheme);
31
+ localStorage.setItem("theme", newTheme);
32
+ document.documentElement.classList.toggle("dark", newTheme === "dark");
33
+ };
34
+
35
+ return (
36
+ <ThemeContext.Provider value={{ theme, toggleTheme }}>
37
+ {children}
38
+ </ThemeContext.Provider>
39
+ );
40
+ }
41
+
42
+ export function useTheme() {
43
+ const context = useContext(ThemeContext);
44
+ if (context === undefined) {
45
+ throw new Error("useTheme must be used within a ThemeProvider");
46
+ }
47
+ return context;
48
+ }
components/ThemeToggle.tsx ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ "use client";
2
+
3
+ import { useTheme } from "./ThemeProvider";
4
+
5
+ export default function ThemeToggle() {
6
+ const { theme, toggleTheme } = useTheme();
7
+
8
+ return (
9
+ <button
10
+ onClick={toggleTheme}
11
+ className="flex h-8 w-8 items-center justify-center rounded-lg bg-gray-200 transition-colors hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600"
12
+ aria-label="Toggle dark mode"
13
+ >
14
+ {theme === "light" ? (
15
+ <svg
16
+ xmlns="http://www.w3.org/2000/svg"
17
+ fill="none"
18
+ viewBox="0 0 24 24"
19
+ strokeWidth={1.5}
20
+ stroke="currentColor"
21
+ className="h-5 w-5"
22
+ >
23
+ <path
24
+ strokeLinecap="round"
25
+ strokeLinejoin="round"
26
+ d="M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z"
27
+ />
28
+ </svg>
29
+ ) : (
30
+ <svg
31
+ xmlns="http://www.w3.org/2000/svg"
32
+ fill="none"
33
+ viewBox="0 0 24 24"
34
+ strokeWidth={1.5}
35
+ stroke="currentColor"
36
+ className="h-5 w-5"
37
+ >
38
+ <path
39
+ strokeLinecap="round"
40
+ strokeLinejoin="round"
41
+ d="M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z"
42
+ />
43
+ </svg>
44
+ )}
45
+ </button>
46
+ );
47
+ }
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
+ }
hooks/useDarkMode.tsx ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 'use client';
2
+
3
+ import { createContext, useContext, useEffect, useState } from 'react';
4
+
5
+ type DarkModeContextType = {
6
+ isDarkMode: boolean;
7
+ toggleDarkMode: () => void;
8
+ };
9
+
10
+ const DarkModeContext = createContext<DarkModeContextType | undefined>(undefined);
11
+
12
+ export function DarkModeProvider({ children }: { children: React.ReactNode }) {
13
+ const [isDarkMode, setIsDarkMode] = useState(false);
14
+
15
+ useEffect(() => {
16
+ // Check if user has dark mode preference
17
+ const isDark = localStorage.getItem('darkMode') === 'true';
18
+ setIsDarkMode(isDark);
19
+ if (isDark) {
20
+ document.documentElement.classList.add('dark');
21
+ }
22
+ }, []);
23
+
24
+ const toggleDarkMode = () => {
25
+ setIsDarkMode((prev) => {
26
+ const newValue = !prev;
27
+ localStorage.setItem('darkMode', String(newValue));
28
+ if (newValue) {
29
+ document.documentElement.classList.add('dark');
30
+ } else {
31
+ document.documentElement.classList.remove('dark');
32
+ }
33
+ return newValue;
34
+ });
35
+ };
36
+
37
+ return (
38
+ <DarkModeContext.Provider value={{ isDarkMode, toggleDarkMode }}>
39
+ {children}
40
+ </DarkModeContext.Provider>
41
+ );
42
+ }
43
+
44
+ export function useDarkMode() {
45
+ const context = useContext(DarkModeContext);
46
+ if (context === undefined) {
47
+ throw new Error('useDarkMode must be used within a DarkModeProvider');
48
+ }
49
+ return context;
50
+ }
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.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
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:017180ef3dccb7e01f8e1e617a736f94bd38c50f7a748b03f1d487e09e3d9f11
3
+ size 100740
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,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Config } from "tailwindcss";
2
+ import colors from "tailwindcss/colors";
3
+ import defaultTheme from "tailwindcss/defaultTheme";
4
+
5
+ const config: Config = {
6
+ darkMode: "class",
7
+ content: [
8
+ "./pages/**/*.{js,ts,jsx,tsx,mdx}",
9
+ "./components/**/*.{js,ts,jsx,tsx,mdx}",
10
+ "./app/**/*.{js,ts,jsx,tsx,mdx}",
11
+ ],
12
+ theme: {
13
+ extend: {
14
+ colors: {
15
+ brand: "#E1E7EC",
16
+ gray: colors.slate,
17
+ },
18
+ backgroundColor: {
19
+ dark: "#0B1120",
20
+ },
21
+ backgroundImage: {
22
+ 'dark-radial': 'radial-gradient(circle at center, rgba(255,255,255,0.1) 0%, transparent 70%)',
23
+ },
24
+ fontFamily: {
25
+ sans: ['"Aeonik"', ...defaultTheme.fontFamily.sans],
26
+ },
27
+ },
28
+ },
29
+ };
30
+
31
+ 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
+ `;