Full Code of alexschachne/leap-ai-avatars for AI

main 0f748b1975b7 cached
19 files
31.0 KB
7.9k tokens
3 symbols
1 requests
Download .txt
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"]
}
Download .txt
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
Download .txt
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.

Copied to clipboard!