Spaces:
Running
Running
osanseviero
commited on
Commit
•
63c7991
1
Parent(s):
44edc3e
Upload 54 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .gitattributes +1 -0
- Dockerfile +20 -0
- LICENSE +21 -0
- README.md +26 -10
- app/(main)/layout.tsx +31 -0
- app/(main)/page.tsx +268 -0
- app/api/generateCode/route.ts +102 -0
- app/api/og/route.tsx +34 -0
- app/favicon.ico +0 -0
- app/globals.css +24 -0
- app/layout.tsx +44 -0
- app/robots.txt +1 -0
- app/share/[id]/layout.tsx +3 -0
- app/share/[id]/page.tsx +52 -0
- components/Footer.tsx +57 -0
- components/Header.tsx +25 -0
- components/code-viewer.css +7 -0
- components/code-viewer.tsx +157 -0
- components/github-icon.tsx +19 -0
- components/loading-dots.module.css +69 -0
- components/loading-dots.tsx +17 -0
- hooks/use-scroll-to.ts +24 -0
- lib/prisma.ts +10 -0
- next-env.d.ts +5 -0
- next.config.mjs +4 -0
- package-lock.json +0 -0
- package.json +45 -0
- postcss.config.mjs +8 -0
- prisma/migrations/20240808195804_first/migration.sql +13 -0
- prisma/migrations/migration_lock.toml +3 -0
- prisma/schema.prisma +18 -0
- public/Aeonik/Aeonik-Bold.ttf +0 -0
- public/Aeonik/Aeonik-Medium.ttf +0 -0
- public/Aeonik/Aeonik-Regular.ttf +0 -0
- public/Aeonik/AeonikFono-Bold.otf +0 -0
- public/Aeonik/AeonikFono-Regular.otf +0 -0
- public/Aeonik/AeonikMono-Regular.otf +0 -0
- public/cssIcon.png +0 -0
- public/favicon.ico +0 -0
- public/halo.png +3 -0
- public/logo.svg +10 -0
- tailwind.config.ts +25 -0
- tsconfig.json +26 -0
- utils/domain.ts +10 -0
- utils/shadcn-docs/avatar.tsx +12 -0
- utils/shadcn-docs/button.tsx +14 -0
- utils/shadcn-docs/card.tsx +27 -0
- utils/shadcn-docs/checkbox.tsx +9 -0
- utils/shadcn-docs/index.ts +23 -0
- 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 |
-
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
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 |
+
`;
|