Repository: alexschachne/leap-ai-avatars
Branch: main
Commit: 0f748b1975b7
Files: 19
Total size: 31.0 KB
Directory structure:
gitextract_p34xlo7p/
├── .envExample
├── .eslintrc.json
├── .gitignore
├── LICENSE.md
├── README.md
├── helpers/
│ └── prompts.ts
├── next.config.js
├── package.json
├── pages/
│ ├── _app.tsx
│ ├── _document.tsx
│ ├── api/
│ │ ├── finetune.ts
│ │ ├── generate.ts
│ │ └── getStatus.ts
│ ├── index.tsx
│ └── src/
│ └── components/
│ └── index/
│ ├── GithubButton.tsx
│ ├── HomeHeader.tsx
│ └── PhotoExamples.tsx
├── styles/
│ └── globals.css
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .envExample
================================================
LEAP_API_KEY=enter-your-key-here
================================================
FILE: .eslintrc.json
================================================
{
"extends": "next/core-web-vitals"
}
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
================================================
FILE: LICENSE.md
================================================
MIT License
Copyright (c) 2023 alexschachne
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Leap AI Avatars ⚡️
Welcome! Bookmark this repo as a starter template for a Headshots or Avatars app built on Leap AI.
It provides a UI for image upload, trains a custom model on Leap, and then generates images of your subject in various styles.
Try it out [here](https://ai-avatars.vercel.app/)!
Let's get started by forking this repository (button top right), and downloading it to your computer. from there follow the below :)
### Run it locally
1. Open the terminal
2. Run `npm install` to grab the necessary packages
3. Hit `npm run dev` to start your server on `http://localhost:3000`
### How to generate images
1. Upload 3-10 photos of yourself
2. Add your API Key from Leap AI
3. Add your model ID from Leap AI to use your existing models (Optional)
### Customizations
1. Head to `pages/index.tsx` for editing text, prompts, and colors to match your theme
2. Adjust prompts and subjectKeyword (ie. @me) in `helpers/prompts.ts`
3. Adjust the number of images generated w/ the numberOfImages parameter in `/pages/api/generate`
### Deployment
1. Push all your changes to Github (or another git provider)
2. Head to vercel.app, import your repo, and hit deploy
3. Note: you will need vercel pro plan or `/pages/api/generate` call will likely timeout after 10 sec. You can also deploy on [Zeet](https://zeet.co/) to avoid this issue.
### Wrapping Up 👏
This is huge! You've got an AI Avatars app running on the web, and you can share it with the world.
If you got value from this -- please give us a star ⭐
Built with [Leap AI](https://tryleap.ai)
================================================
FILE: helpers/prompts.ts
================================================
const keywordIdentifier = "@subject";
const prompts = [
{
label: "Professional",
prompt:
"8k linkedin professional profile photo of " +
keywordIdentifier +
" in a suit with studio lighting, bokeh, corporate portrait headshot photograph best corporate photography photo winner, meticulous detail, hyperrealistic, centered uncropped symmetrical beautiful",
},
{
label: "Psychadelic",
prompt:
"a psychedelic portrait of " +
keywordIdentifier +
", vibrant color scheme, highly detailed, in the style of romanticism, cinematic, artstation, moebius, greg rutkowski",
},
{
label: "Fantasy",
prompt:
"8k portrait of " +
keywordIdentifier +
", d & d, fantasy, intricate, elegant, highly detailed, digital painting, artstation, concept art, matte, sharp focus, illustration, hearthstone, art by artgerm and greg rutkowski and alphonse mucha, 8k",
},
{
label: "Stylish",
prompt:
"80's portrait of " +
keywordIdentifier +
", blue background, white pearl neckless, fashion model in oversized white clothes, official balmain editorial, dramatic lighting highly detailed, analog photo, overglaze, 80mm Sigma f/1.4 or any ZEISS lens",
},
{
label: "Jedi",
prompt:
"8k portrait of " +
keywordIdentifier +
" as a jedi, star wars revenge of the sith movie scene style, studio photography, volumetric lighting, smiling, realistic, 35mm, expressive, iconic, 8k concept art, photorealistic",
},
];
export default prompts;
================================================
FILE: next.config.js
================================================
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
}
module.exports = nextConfig
================================================
FILE: package.json
================================================
{
"name": "leap-template",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@chakra-ui/react": "^2.4.9",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@leap-ai/sdk": "^0.0.119",
"@next/font": "13.1.6",
"@types/multer": "^1.4.7",
"@types/node": "18.13.0",
"@types/react": "18.0.28",
"@types/react-dom": "18.0.10",
"axios": "^1.3.5",
"eslint": "8.34.0",
"eslint-config-next": "13.1.6",
"form-data": "^4.0.0",
"framer-motion": "^9.0.2",
"multer": "^1.4.5-lts.1",
"next": "13.1.6",
"next-connect": "^0.13.0",
"next-seo": "^5.15.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-icons": "^4.7.1",
"react-images-uploading": "^3.1.7",
"typescript": "4.9.5"
}
}
================================================
FILE: pages/_app.tsx
================================================
import "@/styles/globals.css";
import { ChakraProvider } from "@chakra-ui/react";
import type { AppProps } from "next/app";
const App = ({ Component, pageProps }: AppProps) => {
return (
<ChakraProvider>
<Component {...pageProps} />
</ChakraProvider>
);
};
export default App;
================================================
FILE: pages/_document.tsx
================================================
import { Html, Head, Main, NextScript } from 'next/document'
export default function Document() {
return (
<Html lang="en">
<Head />
<body>
<Main />
<NextScript />
</body>
</Html>
)
}
================================================
FILE: pages/api/finetune.ts
================================================
import { NextApiRequest, NextApiResponse } from "next";
import nc from "next-connect";
import multer from "multer";
import FormData from "form-data";
import axios from "axios";
import { Leap } from "@leap-ai/sdk";
const apiRoute = nc<NextApiRequest, NextApiResponse>({
// Handle any other HTTP method
onNoMatch(req, res) {
res.status(405).json({ error: `Method '${req.method}' Not Allowed` });
},
});
const upload = multer({
storage: multer.memoryStorage(),
limits: {
fieldSize: 1024 * 1024 * 10, // 10MB
files: 20,
fieldNameSize: 200,
},
});
apiRoute.post(upload.array("files"), async (req, res) => {
if (req.files === undefined) {
return res.status(400).json({
error: "Missing or invalid file",
});
}
// parse the `body` parameter for apiKey
const body = JSON.parse(req.body.body);
const apiKey = body.apiKey;
// check for api key
const api_key = process.env.API_KEY as string;
const useableKey = apiKey ? apiKey : api_key;
if (!useableKey) {
res.status(400).json({ error: "Invalid request. Check API Key" });
return;
}
// instantiate sdk
const leap = new Leap(useableKey);
// create new model, subjectKeyword is used in prompts.ts to generate images
console.log("Creating New Model...");
const { data: model, error: modelError } = (await leap.fineTune.createModel({
title: "AI Avatars Sample",
subjectKeyword: "@me",
})) as { data: any; error?: any };
// check valid api key
if (modelError) {
console.log("Error: ", modelError.message);
return res.status(400).json({
error: modelError.message,
});
}
console.log("New Model Created: ", model);
const modelId = model?.id;
const subjectKeyword = model?.subjectKeyword;
// now upload the images to fine tune this model using api
const url = `https://api.tryleap.ai/api/v1/images/models/${modelId}/samples`;
const headers = {
accept: "application/json",
Authorization: `Bearer ${useableKey}`,
};
// the way multer handles file uploads, it only supports arrays of files
const filesArray = Array(req.files);
const files = filesArray[0] as any;
console.log(files, "files");
try {
if (req.method === "POST") {
const formData = new FormData();
// here we convert the Multer buffer to a plain old javascript Buffer so that the Leap API can pick it up
for (const file of files) {
console.log(file, "file");
formData.append("files", Buffer.from(file.buffer), file.originalname);
}
const uploadSamplesResponse = await axios.post(url, formData, {
headers,
});
console.log(uploadSamplesResponse.data, "uploadSamplesResponse");
// next queue training job
const { data: newVersion, error: newVersionError } =
(await leap.fineTune.queueTrainingJob({
modelId: modelId,
// webhookUrl: "https://webhook.site/"
})) as { data: any; error?: any };
// check if hit paid API limit or missing samples
if (newVersionError) {
console.log("Error: ", newVersionError.message);
return res.status(400).json({
error: newVersionError.message,
});
}
const versionId = newVersion?.id;
const trainingStatus = newVersion?.status;
console.log("New Training Version: ", newVersion);
console.log("Training Status: ", trainingStatus);
return res.status(200).json({
trainingStatus: trainingStatus,
modelId: modelId,
versionId: versionId,
});
} else {
return res.status(404).send("This method only supports POST requests.");
}
} catch (error: any) {
console.log(error.message);
return res.status(500).json({ error: error.message });
}
});
export const config = {
api: {
bodyParser: false, // Disallow body parsing, consume as stream
},
};
export default apiRoute;
================================================
FILE: pages/api/generate.ts
================================================
import { Leap } from "@leap-ai/sdk";
import { NextApiRequest, NextApiResponse } from "next";
const generate = async (req: NextApiRequest, res: NextApiResponse) => {
// parse the `body` parameter for apiKey, modelId, and versionId
const { apiKey, modelId, prompt } = req.body;
// check for api key
const api_key = process.env.API_KEY as string;
const useableKey = apiKey ? apiKey : api_key;
if (!useableKey) {
res.status(400).json({ error: "Invalid request. Check API Key" });
return;
}
// instantiate sdk
const leap = new Leap(useableKey);
// Now that we have a fine-tuned version of a model, we can generate images using it.
// Make sure subjectKeyword, ie. '@me' is in prompts and loop through prompts to generate images
let avatars = <string[]>[];
const { data: image, error: imageError } = await leap.generate.generateImage({
prompt: prompt,
modelId: modelId,
numberOfImages: 4,
steps: 50,
upscaleBy: "x1",
restoreFaces: true,
});
if (imageError) {
res.status(500).json(imageError);
return;
}
if (image) {
image.images.forEach((image) => {
avatars.push(image.uri);
});
}
res.status(200).json({ avatars: avatars });
};
export default generate;
================================================
FILE: pages/api/getStatus.ts
================================================
import { Leap } from "@leap-ai/sdk";
import { NextApiRequest, NextApiResponse } from "next";
const getStatus = async (req: NextApiRequest, res: NextApiResponse) => {
// parse the `body` parameter for apiKey, modelId, and versionId
const { apiKey, modelId } = req.body;
let { versionId } = req.body;
// check for api key
const api_key = process.env.API_KEY as string;
const useableKey = apiKey ? apiKey : api_key;
if (!useableKey) {
res.status(400).json({ error: "Invalid request. Check API Key" });
return;
}
// instantiate sdk
const leap = new Leap(useableKey);
// check for existing versionId, if not, get first version created
if (!versionId) {
const { data: listModelVersions, error: listModelVersionsError } =
(await leap.fineTune.listModelVersions({
modelId: modelId,
})) as { data: any; error?: any };
if (listModelVersions) {
const existingVersion = listModelVersions[0];
versionId = existingVersion ? existingVersion.id : null;
}
}
// check model training status by continuously polling getModelVersion
const { data: checkStatus, error: checkStatusError } =
(await leap.fineTune.getModelVersion({
modelId: modelId,
versionId: versionId,
})) as { data: any; error?: any };
if (checkStatusError) {
res.status(500).json({ error: checkStatusError });
return;
}
const trainingStatus = checkStatus.status;
res.status(200).json({ trainingStatus: trainingStatus });
};
export default getStatus;
================================================
FILE: pages/index.tsx
================================================
import {
Box,
Button,
Flex,
Heading,
IconButton,
Image,
Input,
InputGroup,
InputRightElement,
Link,
Text,
VStack,
useToast,
} from "@chakra-ui/react";
import { useCallback, useState } from "react";
import { NextSeo } from "next-seo";
import { AiOutlineCloudUpload as UploadIcon } from "react-icons/ai";
import { HiOutlineKey as KeyIcon } from "react-icons/hi";
import ImageUploading, {
ImageListType,
ImageType,
} from "react-images-uploading";
import prompts from "@/helpers/prompts";
import GithubButton from "./src/components/index/GithubButton";
import HomeHeader from "./src/components/index/HomeHeader";
import PhotoExamples from "./src/components/index/PhotoExamples";
interface ImageBatch {
images: string[];
style: string;
}
const Home = () => {
const [apiKey, setApiKey] = useState<string>("");
const [modelId, setModelId] = useState<string>("");
const [loading, setLoading] = useState<boolean>(false);
const [loadingMessage, setLoadingMessage] = useState<string>("Loading...");
const [loadingSubmessage, setLoadingSubmessage] = useState<string>("");
const [uploadImages, setUploadImages] = useState<ImageType[]>([]);
const [imageBatch, setImageBatch] = useState<ImageBatch[]>([]);
const toast = useToast();
// this method generates the avatars
const generate = useCallback(
async (modelId: string, prompt: string) => {
// hit generate endpoint once training finishes
const response = await fetch("/api/generate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
apiKey,
modelId,
prompt,
}),
});
const data = await response.json();
if (data.error) {
window.alert("Error: " + data.error + " " + data.message);
setLoading(false);
return;
}
return data.avatars;
},
[apiKey]
);
// this method gets the training status of the model
const getStatus = useCallback(
async (modelId: string, versionId: string) => {
const response = await fetch("/api/getStatus", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
apiKey,
modelId,
versionId,
}),
});
const status = await response.json();
if (status.error) {
console.log(status.error);
toast({
title: "Error",
description: status.error.message,
status: "error",
});
setLoading(false);
return;
}
return status.trainingStatus;
},
[apiKey, toast]
);
// this method checks if the model is already trained and ready to generate
const checkTrainingDone = useCallback(
async (modelId: string, versionId: string) => {
// now we poll the model status every 10 sec until it's finished
setLoading(true);
let status = "";
while (status !== "finished") {
if (modelId) {
// if no version ID, getStatus will use the latest version
status = await getStatus(modelId, versionId);
if (status === undefined) {
setLoading(false);
return;
}
setLoadingMessage(`Training Status: ${status}`);
setLoadingSubmessage(
`Training takes around 10 minutes. Check back later!`
);
await new Promise((resolve) => setTimeout(resolve, 10000)); // wait for 10 seconds
} else {
setLoading(false);
return;
}
}
if (status === "finished") {
// once training is done, we will generate the images
for (const prompt of prompts) {
setLoadingMessage(`Generating ${prompt.label} Avatars...`);
setLoadingSubmessage(`Takes around 30 seconds. Check back later!`);
const avatars = await generate(modelId, prompt.prompt);
// add the generated images to the image batch
setImageBatch((prevImages) => [
...prevImages,
{ style: prompt.label, images: avatars },
]);
}
}
setLoading(false);
},
[generate, getStatus]
);
// this is method that hits nextjs endpoint to create model, upload samples, and queue training
const finetune = useCallback(async () => {
if (uploadImages.length === 0) {
toast({
title: "Error",
description: "Add a photo first!",
status: "error",
});
return;
}
if (uploadImages.length > 10) {
toast({
title: "Error",
description: "10 photos maximum!",
status: "error",
});
return;
}
// send images to train the model
setLoading(true);
setLoadingMessage("Finetuning model...");
setLoadingSubmessage("Takes around 10 minutes");
const formData = new FormData();
for (const image of uploadImages) {
const file = image.file;
if (file) {
formData.append("files", file);
}
}
formData.append("body", JSON.stringify({ apiKey }));
const response = await fetch("/api/finetune", {
method: "POST",
headers: {
Accept: "application/json",
},
body: formData,
});
// this should be the model id, version id, and trainingStatus == "queued"
const responseData = await response.json();
if (responseData.error) {
toast({
title: "Error",
description: responseData.error + " " + responseData.message,
status: "error",
});
}
checkTrainingDone(responseData.modelId, responseData.versionId);
}, [uploadImages, apiKey, checkTrainingDone, toast]);
return (
<>
<NextSeo
title="Leap AI Avatars"
description="Leap AI Avatars is a web app that uses the Leap AI API to generate AI Avatars. It's built with Next.js, Chakra UI, and Leap AI."
/>
<VStack
minH="100vh"
w="100vw"
spacing={4}
bg="#101219"
px={4}
paddingBottom={"100px"}
fontFamily="monospace"
color={"white"}
>
<HomeHeader />
{imageBatch.length === 0 && !loading && (
<>
<VStack gap={6}>
<Heading
fontSize={"xl"}
fontFamily="monospace"
textAlign={"left"}
>
1) Upload 3-10 photos of yourself
</Heading>
<VStack gap={1}>
<ImageUploading
multiple
value={[]}
onChange={(images: ImageListType) => {
if (images.length === 0) {
return;
}
setUploadImages(images);
}}
dataURLKey="dataURL"
>
{({
imageList,
onImageUpload,
onImageRemoveAll,
onImageUpdate,
onImageRemove,
isDragging,
dragProps,
}) => (
<Flex
borderRadius="lg"
p={8}
maxW="850px"
border={
uploadImages.length === 0 ? "1px dashed #fff" : "none"
}
w="100%"
justifyContent="center"
{...dragProps}
>
<Flex
flexWrap={"wrap"}
alignItems="center"
gridGap={"16px"}
justify={"center"}
>
{uploadImages.map((image, index) => (
<Box
key={index}
className="image-item"
h={100}
position="relative"
maxW={["100px", "100px", "200px"]}
>
<Image
src={image.dataURL}
alt=""
objectFit="contain"
borderRadius={"12px"}
h={"100px"}
border={"2px solid #fff"}
/>
<IconButton
aria-label="Remove Image"
rounded={"full"}
onClick={() =>
// remove image from uploadImages
setUploadImages((prevUploadImages) =>
prevUploadImages.filter((_, i) => i !== index)
)
}
zIndex={2}
icon={
<Image
alt="delete"
width="22px"
src="https://uploads-ssl.webflow.com/630da1fccd22fa1b93dcfa57/6386363c9925b334bd6881fb_remove.svg"
/>
}
color={"#4685f6"}
bg={"white"}
border={"1px solid #1c1c1c"}
h={25}
w={25}
minW={25}
pos={"absolute"}
top={-2}
right={-2}
/>
</Box>
))}
</Flex>
{uploadImages.length === 0 && (
<Flex
flexDirection={"column"}
justify={"center"}
alignItems={"center"}
wrap={"wrap"}
cursor={"pointer"}
onClick={onImageUpload}
>
<UploadIcon size={40} />
<Text
fontSize={"1.5em"}
mt="10px"
textAlign={"center"}
>
Click or Drop Images
</Text>
</Flex>
)}
</Flex>
)}
</ImageUploading>
<PhotoExamples />
</VStack>
<Heading
fontSize={"xl"}
fontFamily="monospace"
textAlign={"left"}
>
2) Add your API Key from{" "}
<Link
target="_blank"
href="https://tryleap.ai"
textDecoration={"underline"}
>
Leap AI
</Link>{" "}
</Heading>
<InputGroup size="md" w={{ base: "full", md: "30rem" }}>
<Input
py={4}
borderColor={"red.200"}
color="gray.100"
focusBorderColor="gray.100"
variant="outline"
onChange={(e) => setApiKey(e.target.value)}
value={apiKey}
placeholder="Add your API KEY here"
/>
<InputRightElement width="3rem">
<IconButton
onClick={() => {
window.open("https://tryleap.ai", "_blank");
}}
aria-label="key"
icon={<KeyIcon />}
/>
</InputRightElement>
</InputGroup>
<Heading
fontSize={"xl"}
fontFamily="monospace"
textAlign={"left"}
>
3) (Optional) add your model ID from{" "}
<Link
target="_blank"
href="https://tryleap.ai"
textDecoration={"underline"}
>
Leap AI
</Link>{" "}
</Heading>
<Input
w={{ base: "full", md: "30rem" }}
py={4}
color="gray.100"
focusBorderColor="gray.100"
variant="outline"
onChange={(e) => setModelId(e.target.value)}
value={modelId}
placeholder="Optional model ID to use existing model"
/>
</VStack>
</>
)}
{imageBatch.map((batch, index) => (
<Box key={index}>
<Text
fontSize={"1.5em"}
textAlign={"left"}
fontWeight={"bold"}
mb={4}
>
{batch.style} Avatars
</Text>
<Flex
w={{ base: "full", md: "50rem" }}
gridGap={"16px"}
h="auto"
flexDir={"row"}
mb={4}
>
{batch.images.map((image) => (
<Box key={image} className="image-item" position="relative">
<Image
src={image}
alt=""
objectFit="contain"
borderRadius={"12px"}
/>
</Box>
))}
</Flex>
</Box>
))}
{loading && (
<>
<Text fontSize={"1.5em"} textAlign={"center"} fontWeight={"bold"}>
{loadingMessage}
</Text>
<Text mt={0} fontSize="md" fontWeight="bold">
{loadingSubmessage}
</Text>
</>
)}
<Button
w={{ base: "full", md: "30rem" }}
_hover={loading ? {} : { opacity: 0.8 }}
_active={loading ? {} : { transform: "scale(0.98)", opacity: 0.7 }}
transitionDuration="200ms"
bg="#46ad37"
color="white"
p={2}
rounded="lg"
fontSize="lg"
onClick={() => {
if (modelId) {
checkTrainingDone(modelId, "");
} else {
finetune();
}
}}
isLoading={loading}
>
Get AI Avatars
</Button>
<GithubButton />
<Box
pos={"fixed"}
bottom={0}
w={"100%"}
bg={"#fff"}
color={"#1c1c1c"}
zIndex={999}
p={2}
>
<Box textAlign="center" fontSize="xl">
<Text fontSize="xs" fontWeight="bold">
Built with{" "}
<Link
target="_blank"
href="https://tryleap.ai"
textDecoration={"underline"}
>
Leap API
</Link>{" "}
by{" "}
<Link target="_blank" href="https://twitter.com/thealexshaq">
alex
</Link>
. Create your own AI Avatars app{" "}
<Link
target="_blank"
href="https://github.com/alexschachne/leap-ai-avatars"
textDecoration={"underline"}
>
here
</Link>{" "}
🚀
</Text>
</Box>
</Box>
</VStack>
</>
);
};
export default Home;
================================================
FILE: pages/src/components/index/GithubButton.tsx
================================================
import { HStack, Text } from "@chakra-ui/react";
import { AiFillGithub } from "react-icons/ai";
const GithubButton = () => {
return (
<HStack
bg="white"
p={4}
py={2}
rounded="md"
_hover={{ opacity: 0.8 }}
_active={{ transform: "scale(0.99)", opacity: 0.7 }}
cursor="pointer"
transitionDuration="200ms"
pos="absolute"
top={0}
right={4}
onClick={() =>
window.open("https://github.com/alexschachne/leap-ai-avatars")
}
>
<AiFillGithub color="black" />
<Text fontWeight={"bold"} color={"#1c1c1c"}>
Fork the code on GitHub
</Text>
</HStack>
);
};
export default GithubButton;
================================================
FILE: pages/src/components/index/HomeHeader.tsx
================================================
import { Heading, VStack } from "@chakra-ui/react";
import Link from "next/link";
const HomeHeader = () => {
return (
<VStack spacing={1} mb={2}>
<Heading
pt={{
base: 20,
md: 20,
}}
mb={3}
color="gray.200"
fontFamily="monospace"
>
🔥{" "}
<Link target="_blank" href="https://tryleap.ai">
Leap AI
</Link>{" "}
Avatars 🔥
</Heading>
<Heading fontFamily="monospace" fontSize={"2xl"}>
Get your Avatars in 3 easy steps
</Heading>
</VStack>
);
};
export default HomeHeader;
================================================
FILE: pages/src/components/index/PhotoExamples.tsx
================================================
import { Flex, Image } from "@chakra-ui/react";
const PhotoExamples = () => {
return (
<Flex
borderWidth="0.5px"
borderColor={"white"}
bg="white"
borderRadius="lg"
maxW="850px"
w="100%"
p={2}
justifyContent="center"
flexDirection={"row"}
flexWrap={["wrap", "wrap", "nowrap"]}
>
{" "}
<Image
src={
"https://uploads-ssl.webflow.com/631e7debd95a0a0b974074e2/64303d1f1db19e6b53c53043_IMG_8601.jpg"
}
alt="goodExample"
rounded="lg"
w="100%"
maxH={"200px"}
objectFit="contain"
/>
<Image
src={
"https://uploads-ssl.webflow.com/631e7debd95a0a0b974074e2/64303d1fd5c80d1b91102d00_IMG_8601%202.jpg"
}
alt="badExample"
rounded="lg"
w="100%"
maxH={"200px"}
objectFit="contain"
/>
</Flex>
);
};
export default PhotoExamples;
================================================
FILE: styles/globals.css
================================================
/* load custom font! */
@font-face {
font-family: "mariofont";
src: url("mario.woff") format("woff");
font-weight: normal;
font-style: normal;
font-display: swap;
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"baseUrl": ".",
"paths": {
"@/*": ["./*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
"exclude": ["node_modules"]
}
gitextract_p34xlo7p/ ├── .envExample ├── .eslintrc.json ├── .gitignore ├── LICENSE.md ├── README.md ├── helpers/ │ └── prompts.ts ├── next.config.js ├── package.json ├── pages/ │ ├── _app.tsx │ ├── _document.tsx │ ├── api/ │ │ ├── finetune.ts │ │ ├── generate.ts │ │ └── getStatus.ts │ ├── index.tsx │ └── src/ │ └── components/ │ └── index/ │ ├── GithubButton.tsx │ ├── HomeHeader.tsx │ └── PhotoExamples.tsx ├── styles/ │ └── globals.css └── tsconfig.json
SYMBOL INDEX (3 symbols across 3 files)
FILE: pages/_document.tsx
function Document (line 3) | function Document() {
FILE: pages/api/finetune.ts
method onNoMatch (line 12) | onNoMatch(req, res) {
FILE: pages/index.tsx
type ImageBatch (line 33) | interface ImageBatch {
Condensed preview — 19 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (34K chars).
[
{
"path": ".envExample",
"chars": 32,
"preview": "LEAP_API_KEY=enter-your-key-here"
},
{
"path": ".eslintrc.json",
"chars": 40,
"preview": "{\n \"extends\": \"next/core-web-vitals\"\n}\n"
},
{
"path": ".gitignore",
"chars": 385,
"preview": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pn"
},
{
"path": "LICENSE.md",
"chars": 1069,
"preview": "MIT License\n\nCopyright (c) 2023 alexschachne\n\nPermission is hereby granted, free of charge, to any person obtaining a co"
},
{
"path": "README.md",
"chars": 1567,
"preview": "# Leap AI Avatars ⚡️\n\nWelcome! Bookmark this repo as a starter template for a Headshots or Avatars app built on Leap AI."
},
{
"path": "helpers/prompts.ts",
"chars": 1550,
"preview": "const keywordIdentifier = \"@subject\";\nconst prompts = [\n {\n label: \"Professional\",\n prompt:\n \"8k linkedin pr"
},
{
"path": "next.config.js",
"chars": 118,
"preview": "/** @type {import('next').NextConfig} */\nconst nextConfig = {\n reactStrictMode: true,\n}\n\nmodule.exports = nextConfig\n"
},
{
"path": "package.json",
"chars": 903,
"preview": "{\n \"name\": \"leap-template\",\n \"version\": \"0.1.0\",\n \"private\": true,\n \"scripts\": {\n \"dev\": \"next dev\",\n \"build\":"
},
{
"path": "pages/_app.tsx",
"chars": 297,
"preview": "import \"@/styles/globals.css\";\nimport { ChakraProvider } from \"@chakra-ui/react\";\nimport type { AppProps } from \"next/ap"
},
{
"path": "pages/_document.tsx",
"chars": 231,
"preview": "import { Html, Head, Main, NextScript } from 'next/document'\n\nexport default function Document() {\n return (\n <Html "
},
{
"path": "pages/api/finetune.ts",
"chars": 3903,
"preview": "import { NextApiRequest, NextApiResponse } from \"next\";\nimport nc from \"next-connect\";\nimport multer from \"multer\";\nimpo"
},
{
"path": "pages/api/generate.ts",
"chars": 1249,
"preview": "import { Leap } from \"@leap-ai/sdk\";\nimport { NextApiRequest, NextApiResponse } from \"next\";\n\nconst generate = async (re"
},
{
"path": "pages/api/getStatus.ts",
"chars": 1524,
"preview": "import { Leap } from \"@leap-ai/sdk\";\nimport { NextApiRequest, NextApiResponse } from \"next\";\n\nconst getStatus = async (r"
},
{
"path": "pages/index.tsx",
"chars": 15823,
"preview": "import {\n Box,\n Button,\n Flex,\n Heading,\n IconButton,\n Image,\n Input,\n InputGroup,\n InputRightElement,\n Link,\n"
},
{
"path": "pages/src/components/index/GithubButton.tsx",
"chars": 700,
"preview": "import { HStack, Text } from \"@chakra-ui/react\";\nimport { AiFillGithub } from \"react-icons/ai\";\n\nconst GithubButton = ()"
},
{
"path": "pages/src/components/index/HomeHeader.tsx",
"chars": 620,
"preview": "import { Heading, VStack } from \"@chakra-ui/react\";\nimport Link from \"next/link\";\n\nconst HomeHeader = () => {\n return ("
},
{
"path": "pages/src/components/index/PhotoExamples.tsx",
"chars": 951,
"preview": "import { Flex, Image } from \"@chakra-ui/react\";\n\nconst PhotoExamples = () => {\n return (\n <Flex\n borderWidth=\"0"
},
{
"path": "styles/globals.css",
"chars": 175,
"preview": "/* load custom font! */\n@font-face {\n font-family: \"mariofont\";\n src: url(\"mario.woff\") format(\"woff\");\n font-weight:"
},
{
"path": "tsconfig.json",
"chars": 572,
"preview": "{\n \"compilerOptions\": {\n \"target\": \"es5\",\n \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n \"allowJs\": true,\n \"sk"
}
]
About this extraction
This page contains the full source code of the alexschachne/leap-ai-avatars GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 19 files (31.0 KB), approximately 7.9k tokens, and a symbol index with 3 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.