Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added feature for multiple file support, routing, and Export file. #14

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@

## Cloning & running

1. Clone the repo: `git clone https://github.com/Nutlope/llamacoder`
1. Clone the repo: `git clone https://github.com/iamthehimansh/llamacoder`
2. Create a `.env` file and add your [Together AI API key](https://dub.sh/together-ai): `TOGETHER_API_KEY=`
3. Run `npm install` and `npm run dev` to install dependencies and run locally

## Future Tasks

- [ ] Look into a way to export/deploy the app in a single click. Can try to do it myself with a dynamic route + some hashing, or try to use Replit/Vercel
- [ ] Look into a way to deploy the app in a single click. Can try to do it myself with a dynamic route + some hashing, or try to use Replit/Vercel
- [ ] Save previous versions so people can go back and forth between the generated ones
- [ ] Could be nice to show a "featured apps" route on the site on `/featured`. Have a `/id/${prompt}` dynamic route that can display a bunch of nice example apps in the sandbox ready to go
- [ ] Support more languages starting with Python, check out E2B
Expand Down
37 changes: 34 additions & 3 deletions app/api/generateCode/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,45 @@ import {
export const maxDuration = 60;

const systemPrompt = `
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:
You are an expert frontend React engineer(15 year of exprience) who is also a great UI/UX designer. Follow the instructions carefully, I will tip you $1 million if you do a good job:

- 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
- Create React components for whatever the user asked you to create and make sure it can run by itself by using a default export
- Make sure the React app is interactive and functional by creating state when needed and having no required props
- Use TypeScript as the language for the React component
- Use Tailwind classes for styling. DO NOT USE ARBITRARY VALUES (e.g. \`h-[600px]\`). Make sure to use a consistent color palette.
- 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.
- NO OTHER LIBRARIES (e.g. zod, hookform) ARE INSTALLED OR ABLE TO BE IMPORTED.
- NO OTHER LIBRARIES (e.g. zod, hookform) ARE INSTALLED OR ABLE TO BE IMPORTED EXCEPT "react-router-dom".
- 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 \`\`\`.
- You can also create many files if needed, but make sure to export the main component from the main file.
- Make sure to write the name of File and Component in the comment at the top of the file, e.g. \`//Button.tsx\`
- Separate the files by using the following structure: -%-%- (e.g. \`//Button.tsx MultilineContents... -%-%- //ButtonGroup.tsx MultilineContents...\`)
- Always make sure to Start app from 'App.tsx' (then main file which will be mounted in 'index.tsx')
- Add .tsx extension to all files when importing in another file
- You can generate as many files as you need, but make sure to export the component from the file.
- You can genrate any text-based files. For example, if you need to generate a CSS file, you can generate a CSS file with the content you need.
- We have installed the following libraries for you:
- lucide-react: For icons
- react-router-dom: For routing
- recharts: For charts
- @mui/material: For UI components
- @emotion/react: For styling
- @emotion/styled: For styling
- @mui/icons-material: For icons
- @headlessui/react: For UI components
- @heroicons/react: For icons (V2 only)
- @radix-ui/react-tooltip: For tooltips
- @radix-ui/react-select: For select components
- framer-motion: For animations
- react-icons: For icons
- react-spring: For animations
You can use these libraries in your app if needed.
- Always make ui beautiful and responsive and animated(if not mentioned by user)
- Remember, you are an expert frontend React engineer(15 year of exprience) who is also a great UI/UX designer. So, make sure to write the code as an expert frontend React engineer(15 year of exprience) who is also a great UI/UX designer.
- Use component from the libraries mentioned above if needed.
- Do not use any other libraries.
- Do not use Link from react-router-dom, until unless you defined routes and the component should be inside the provider.
- For images, you can use any image from the internet (do not use local one).
- Do not use these kind of stuff ""import logo = 'https://via.placeholder.com/150';"" instead create a variable storing the link or hardcord it.
`;

export async function POST(req: Request) {
Expand All @@ -37,6 +67,7 @@ export async function POST(req: Request) {
],
stream: true,
temperature: 0.2,
max_tokens: 4097,
};
const stream = await TogetherAIStream(payload);

Expand Down
165 changes: 127 additions & 38 deletions app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,74 @@ import {
} from "eventsource-parser";
import { AnimatePresence, motion } from "framer-motion";
import { FormEvent, useEffect, useState } from "react";
import LoadingDots from "../components/loading-dots";
import LoadingDots from "@/components/loading-dots";
import ExportToZip from "@/utils/ExportToZip"
import ReactAllFiles from "@/utils/ReactAllFiles";
import sendToSrc from "@/utils/SrcCode";
const CODES = {
"App.tsx": "",
"/public/index.html": `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>`,
};

export default function Home() {
let [status, setStatus] = useState<
"initial" | "creating" | "created" | "updating" | "updated"
>("initial");
let [generatedCode, setGeneratedCode] = useState("");
let [CodeFiles, setCodeFiles] = useState(CODES);
let [modelUsedForInitialCode, setModelUsedForInitialCode] = useState("");
let [ref, scrollTo] = useScrollTo();
let [activeFile, setActiveFile] = useState<String>("App.tsx");
let [messages, setMessages] = useState<{ role: string; content: string }[]>(
[],
[]
);
const [downloadloading, setDownloadLoading] = useState(false);
// TODO: Dynamically add dependencies based on the model
const dependencies = {
"lucide-react": "latest",
"react-router-dom": "latest",
recharts: "2.9.0",
"@mui/material": "latest",
"@emotion/react":"latest",
"@emotion/styled":"latest",
"@mui/icons-material":"latest",
// other ui libraries
"@headlessui/react": "latest",
"@heroicons/react": "latest",
"@radix-ui/react-tooltip": "latest",
"@radix-ui/react-select": "latest",
"framer-motion": "latest",
"react-icons": "latest",
// "@chakra-ui/react": "latest",
"react-spring": "latest",
"@fortawesome/react-fontawesome": "latest",



}

let loading = status === "creating" || status === "updating";

function SetCodesFiless(data: any) {
try {
const text = JSON.parse(data).text ?? "";
setGeneratedCode((prev) => prev + text);
} catch (e) {
console.error(e);
}
}

async function generateCode(e: FormEvent<HTMLFormElement>) {
e.preventDefault();

Expand Down Expand Up @@ -67,24 +120,17 @@ export default function Home() {
throw new Error(chatRes.statusText);
}

// This data is a ReadableStream
const data = chatRes.body;
if (!data) {
return;
}
const onParse = (event: ParsedEvent | ReconnectInterval) => {
if (event.type === "event") {
const data = event.data;
try {
const text = JSON.parse(data).text ?? "";
setGeneratedCode((prev) => prev + text);
} catch (e) {
console.error(e);
}
SetCodesFiless(data);
}
};

// https://web.dev/streams/#the-getreader-and-read-methods
const reader = data.getReader();
const decoder = new TextDecoder();
const parser = createParser(onParse);
Expand All @@ -106,7 +152,30 @@ export default function Home() {
setMessages(newMessages);
setStatus("created");
}
async function handleExport() {
setDownloadLoading(true);
let name="App";
for (let i = 0; i < messages.length; i++) {
if (messages[i].role === "user") {
name = messages[i].content;
}
}
const updetedName=name.replaceAll(" ","-").replaceAll(".","-").replaceAll("/","-").replaceAll("\\","-").replaceAll(":","-").replaceAll("*","-").replaceAll("?","-").replaceAll("\"","-").replaceAll("<","-").replaceAll(">","-").replaceAll("|","-")
const files={
...sendToSrc(CodeFiles),
...ReactAllFiles(updetedName.toLowerCase(), name, dependencies),
}
console.log(files);
try{
await ExportToZip(files,updetedName);

}catch{
await ExportToZip(files,"App");
}
setDownloadLoading(false);


}
async function modifyCode(e: FormEvent<HTMLFormElement>) {
e.preventDefault();

Expand Down Expand Up @@ -134,24 +203,17 @@ export default function Home() {
throw new Error(chatRes.statusText);
}

// This data is a ReadableStream
const data = chatRes.body;
if (!data) {
return;
}
const onParse = (event: ParsedEvent | ReconnectInterval) => {
if (event.type === "event") {
const data = event.data;
try {
const text = JSON.parse(data).text ?? "";
setGeneratedCode((prev) => prev + text);
} catch (e) {
console.error(e);
}
SetCodesFiless(data);
}
};

// https://web.dev/streams/#the-getreader-and-read-methods
const reader = data.getReader();
const decoder = new TextDecoder();
const parser = createParser(onParse);
Expand Down Expand Up @@ -179,8 +241,39 @@ export default function Home() {
let end = el.scrollHeight - el.clientHeight;
el.scrollTo({ top: end });
}
let activeFile_="App.tsx";
try {
const codeFiles = generatedCode.split("-%-%-");
const newCodeFiles: { [key: string]: string } = {};
for (let i = 0; i < codeFiles.length; i++) {
const codeFile = codeFiles[i];
const regex = /\/\/(.*).tsx/g;
const match = regex.exec(codeFile);
if (match) {
const fileName = match[1];
const fileContent = codeFile.replace(match[0], "");
if( fileName.trim() === "index") {
continue;
}
const fullName=fileName.trim() + ".tsx";
newCodeFiles[fullName] = fileContent;
activeFile_=fullName;
}
}
setCodeFiles((prevCodeFiles) => ({
...CODES,
...prevCodeFiles,
...newCodeFiles,
}));
setActiveFile(activeFile_);
} catch (e) {
console.error(e);
}
}, [loading, generatedCode]);

useEffect(() => {
console.log("CodeFiles",CodeFiles);
},[CodeFiles]);
return (
<div className="mx-auto flex min-h-screen max-w-7xl flex-col items-center justify-center py-2">
<Header />
Expand Down Expand Up @@ -359,39 +452,35 @@ export default function Home() {
</div>
<div className="relative mt-8 w-full overflow-hidden">
<div className="isolate">

<Sandpack
theme={draculaTheme}
options={{
showNavigator: true,
externalResources: [
"https://unpkg.com/@tailwindcss/ui/dist/tailwind-ui.min.css",

],
editorHeight: "80vh",
showTabs: false,
}}
files={{
"App.tsx": generatedCode,
"/public/index.html": `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>`,
showTabs: true,
// @ts-ignore
activeFile:activeFile,


}}
files={CodeFiles}
template="react-ts"
customSetup={{
dependencies: {
"lucide-react": "latest",
recharts: "2.9.0",
},
dependencies: dependencies,
}}

/>
{!loading&&<button
onClick={handleExport}
className="inline-flex items-center justify-center px-4 py-2 mt-4 text-sm font-medium text-white bg-blue-500 border border-transparent rounded-md hover:bg-blue-600 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
>
{!downloadloading? "Download App":<LoadingDots color="white" style="small" />}
</button>}
</div>

<AnimatePresence>
Expand Down
2 changes: 1 addition & 1 deletion components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export default function Footer() {
</svg>
</Link>
<Link
href="https://github.com/Nutlope/llamacoder"
href="https://github.com/iamthehimansh/llamacoder"
className="group"
aria-label="TaxPal on GitHub"
>
Expand Down
2 changes: 1 addition & 1 deletion components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export default function Header() {
</h1>
</Link>
<a
href="https://github.com/nutlope/llamacoder"
href="https://github.com/iamthehimansh/llamacoder"
target="_blank"
className="ml-auto hidden items-center gap-3 rounded-2xl bg-white px-6 py-2 sm:flex"
>
Expand Down
Loading