Build an AI-Powered Resume & Cover Letter Generator (CopilotKit, LangChain, Tavily & Next.js)
Tags: webdev, tutorial, programming, javascript
Published: 2024-06-26 13:40:20

## **TL;DR**
Building a great project is the best resume for an aspiring developer. 

Well, today we will hit two birds with one stone; I will teach you how to build a cutting-edge, AI-powered application that will generate your resume & cover letter based on your LinkedIn, GitHub & X. 

This project & your ensuing resume will blow away any employer. 


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bty1rqdcu4wknb2ws0w4.gif)


We'll cover how to:

- Build the resume & cover letter generator web app using Next.js, TypeScript, and Tailwind CSS.
- Use CopilotKit to integrate AI functionalities into the resume & cover letter generator.
- Use Langchain and Tavily to scrape your LinkedIn, GitHub, or X profile content.


---

# CopilotKit: The open-source framework for building in-app AI copilots

CopilotKit is an [open-source AI copilot platform](https://github.com/CopilotKit/CopilotKit). We make it easy to integrate powerful AI into your React apps.

Build:

- ChatBot: Context-aware in-app chatbots that can take actions in-app 💬
- CopilotTextArea: AI-powered textFields with context-aware autocomplete & insertions 📝
- Co-Agents: In-app AI agents that can interact with your app & users 🤖

![https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3us3vc140aun0dvrdof.gif](https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx3us3vc140aun0dvrdof.gif)

{% cta https://git.new/devtoarticle1 %} Star CopilotKit ⭐️ {% endcta %}

---



## Prerequisites

To fully understand this tutorial, you need to have a basic understanding of React or Next.js.

Here are the tools required to build the AI-powered resume and cover letter generator:

- [React Markdown](https://github.com/remarkjs/react-markdown) -  a **React** component that can be given a string of markdown to safely render to React elements.
- [Langchain](https://www.langchain.com/) - provides a framework that enables AI agents to search the web, research and scrape any topic or link.
- [OpenAI API](https://platform.openai.com/api-keys) - provides an API key that enables you to carry out various tasks using ChatGPT models.
- [Tavily AI](https://tavily.com/) - a search engine that enables AI agents to conduct research or scrape data and access real-time knowledge within the application.
- [CopilotKit](https://github.com/CopilotKit) - an open-source copilot framework for building custom AI chatbots, in-app AI agents, and text areas.

## Project Set up and Package Installation

First, create a Next.js application by running the code snippet below in your terminal:

```tsx
npx create-next-app@latest airesumecoverlettergenerator
```

Select your preferred configuration settings. For this tutorial, we'll be using TypeScript and Next.js App Router.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uhgnfrg24f5v54vons0d.png)

Next, install the React Markdown and OpenAI packages with their dependencies.

```jsx
npm i react-markdown openai
```

Finally, install the CopilotKit packages. These packages enable us to retrieve data from the React state and add AI copilot to the application.

```jsx
npm install @copilotkit/react-ui @copilotkit/react-core @copilotkit/backend
```

Congratulations! You're now ready to build an AI-powered resume and cover letter generator.

## **Building The Resume & Cover Letter Generator Frontend**

In this section, I will walk you through the process of creating the Resume & cover letter generator frontend with static content to define the generator’s user interface.

To get started, go to `/[root]/src/app` in your code editor and create a folder called `components`. Inside the components folder, create a file named `Resume.tsx` 

In the `Resume.tsx` file, add the following code that defines a React functional component called **`Resume`**.

```jsx
"use client";

// Import React and necessary hooks from the react library
import React from "react";
import { useState } from "react";
// Import the ReactMarkdown component to render markdown content
import ReactMarkdown from "react-markdown";
// Import the Link component from Next.js for navigation
import Link from "next/link";

function Resume() {
  // State variables to store the resume and cover letter content
  const [coverLetter, setCoverLetter] = useState("");
  const [resume, setResume] = useState("");

  return (
    // Main container with flex layout, full width, and minimum height of screen
    <div className="flex flex-col w-full min-h-screen bg-gray-100 dark:bg-gray-800">
      {/* Header section with a fixed height, padding, and border at the bottom */}
      <header className="flex items-center h-16 px-4 border-b shrink-0 md:px-6 bg-white dark:bg-gray-900">
        {/* Link component for navigation with custom styles */}
        <Link
          href="#"
          className="flex items-center gap-2 text-lg font-semibold md:text-base"
          prefetch={false}>
          <span className="sr-only text-gray-500">Resume Dashboard</span>
          <h1>Resume & Cover Letter Generator</h1>
        </Link>
      </header>
      {/* Main content area with padding */}
      <main className="flex-1 p-4 md:p-8 lg:p-10">
        {/* Container for the content with maximum width and centered alignment */}
        <div className="max-w-4xl mx-auto grid gap-8">
          {/* Section for displaying the resume */}
          <section>
            <div className="bg-white dark:bg-gray-900 rounded-lg shadow-sm">
              <div className="p-6 md:p-8">
                <h2 className="text-lg font-bold">Resume</h2>
                <div className="my-6" />
                <div className="grid gap-6">
                  {/* Conditional rendering of the resume content */}
                  {resume ? (
                    <ReactMarkdown>{resume}</ReactMarkdown>
                  ) : (
                    <div>No Resume To Display</div>
                  )}
                </div>
              </div>
            </div>
          </section>
          {/* Section for displaying the cover letter */}
          <section>
            <div className="bg-white dark:bg-gray-900 rounded-lg shadow-sm">
              <div className="p-6 md:p-8">
                <h2 className="text-lg font-bold">Cover Letter</h2>
                <div className="my-6" />
                <div className="grid gap-4">
                  {/* Conditional rendering of the cover letter content */}
                  {coverLetter ? (
                    <ReactMarkdown>{coverLetter}</ReactMarkdown>
                  ) : (
                    <div>No Cover Letter To Display</div>
                  )}
                </div>
              </div>
            </div>
          </section>
        </div>
      </main>
    </div>
  );
}

export default Resume;
```

Next, go to `/[root]/src/page.tsx` file, and add the following code that imports `Resume` component and defines a functional component named `Home`.

```jsx
import Resume from "./components/Resume";

export default function Home() {
  return <Resume />;
}
```

Finally, run the command `npm run dev` on the command line and then navigate to http://localhost:3000/.

Now you should view the resume and cover letter generator frontend on your browser, as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7twcr2xhw15s9x9zz88k.png)

Congratulations! You're now ready to add AI functionalities to the AI-powered resume and cover letter generator.

## **Integrating AI Functionalities To The Resume & Cover Letter Generator Using CopilotKit**

In this section, you will learn how to add an AI copilot to the Resume & Cover Letter generator to generate resume and cover letter using CopilotKit.

CopilotKit offers both frontend and [backend](https://docs.copilotkit.ai/getting-started/quickstart-backend) packages. They enable you to plug into the React states and process application data on the backend using AI agents.

First, let's add the CopilotKit React components to the Resume & Cover Letter generator frontend.

### **Adding CopilotKit to the To-Do List Generator Frontend**

Here, I will walk you through the process of integrating the Resume & Cover Letter generator with the CopilotKit frontend to facilitate Resume & Cover Letter generation.

To get started, use the code snippet below to import `useCopilotReadable`, and `useCopilotAction`, custom hooks at the top of the `/src/app/components/Resume.tsx` file.

```jsx
import { useCopilotAction, useCopilotReadable } from "@copilotkit/react-core";
```

Inside the `Resume` function, below the state variables, add the following code that uses the `useCopilotReadable` hook to add the Resume & Cover Letter that will be generated as context for the in-app chatbot. The hook makes the Resume & Cover Letter readable to the copilot.

```jsx
useCopilotReadable({
    description: "The user's cover letter.",
    value: coverLetter,
  });

  useCopilotReadable({
    description: "The user's resume.",
    value: resume,
  });
```

Below the code above, add the following code that uses the `useCopilotAction` hook to set up an action called `createCoverLetterAndResume` which will enable the generation of resume and cover letter.

The action takes two parameters called `coverLetterMarkdown` and `resumeMarkdown` which enables the generation of resume and cover letter. It contains a handler function that generates resume and cover letter based on a given prompt.

Inside the handler function, `coverLetter` and `resume` states are updated with the newly generated resume and cover letter markdown, as shown below.

```tsx
useCopilotAction(
  {
    // Define the name of the action
    name: "createCoverLetterAndResume",
    // Provide a description for the action
    description: "Create a cover letter and resume for a job application.",
    // Define the parameters required for the action
    parameters: [
      {
        // Name of the first parameter
        name: "coverLetterMarkdown",
        // Type of the first parameter
        type: "string",
        // Description of the first parameter
        description:
          "Markdown text for a cover letter to introduce yourself and briefly summarize your professional background.",
        // Mark the first parameter as required
        required: true,
      },
      {
        // Name of the second parameter
        name: "resumeMarkdown",
        // Type of the second parameter
        type: "string",
        // Description of the second parameter
        description:
          "Markdown text for a resume that displays your professional background and relevant skills.",
        // Mark the second parameter as required
        required: true,
      },
    ],
    // Define the handler function to be executed when the action is called
    handler: async ({ coverLetterMarkdown, resumeMarkdown }) => {
      // Update the state with the provided cover letter markdown text
      setCoverLetter(coverLetterMarkdown);
      // Update the state with the provided resume markdown text
      setResume(resumeMarkdown);
    },
  },
  // Empty dependency array, indicating this effect does not depend on any props or state
  [],
);
```

After that, go to `/[root]/src/app/page.tsx` file and import CopilotKit frontend packages and styles at the top using the code below.

```jsx
import { CopilotKit } from "@copilotkit/react-core";
import { CopilotSidebar } from "@copilotkit/react-ui";
import "@copilotkit/react-ui/styles.css";
```

Then use `CopilotKit` to wrap the `CopilotSidebar` and `Resume` components, as shown below. The `CopilotKit` component specifies the URL for CopilotKit's backend endpoint (`/api/copilotkit/`) while the `CopilotSidebar` renders the in-app chatbot that you can give prompts to generate resume and cover letter.

```jsx
export default function Home() {
  return (
    <CopilotKit runtimeUrl="/api/copilotkit">
      <CopilotSidebar
        instructions={"Help the user create a cover letter and resume"}
        labels={{
          initial:
            "Welcome to the cover letter app! Add your LinkedIn, X, or GitHub profile link below.",
        }}
        defaultOpen={true}
        clickOutsideToClose={false}>
        <Resume />
      </CopilotSidebar>
    </CopilotKit>
  );
}
```

After that, run the development server and navigate to [http://localhost:3000](http://localhost:3000/). You should see that the in-app chatbot was integrated into the Resume and Cover Letter generator.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b3dbqyfqzhjj0dw8imv7.png)

### **Adding CopilotKit Backend to the Blog**

Here, I will walk you through the process of integrating the Resume and Cover Letter generator with the CopilotKit backend that handles requests from frontend, and provides function calling and various LLM backends such as GPT.

Also, we will integrate an AI agent named Tavily that can scrape content on any given link on the web.

To get started, create a file called `.env.local` in the root directory. Then add the environment variables below in the file that hold your `ChatGPT` and `Tavily` Search API keys.

```bash

OPENAI_API_KEY="Your ChatGPT API key"
TAVILY_API_KEY="Your Tavily Search API key"
OPENAI_MODEL=gpt-4-1106-preview
```

To get the ChatGPT API key, navigate to https://platform.openai.com/api-keys.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tblh9suj8dsp6tab3ej0.jpg)

To get the Tavily Search API key, navigate to https://app.tavily.com/home


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9comyk1kt8mibhvhtpq3.jpg)

After that, go to `/[root]/src/app` and create a folder called `api`. In the `api` folder, create a folder called `copilotkit`.

In the `copilotkit` folder, create a file called `tavily.ts` and add the following code. The code defines an asynchronous function **`scrape`** that takes a link as input, sends this link to Tavily API, processes the JSON response, and then uses OpenAI's language model to generate a summary of the response in plain English.

```jsx
// Import the OpenAI library
import OpenAI from "openai";

// Define an asynchronous function named `scrape` that takes a search query string as an argument
export async function scrape(query: string) {
  // Send a POST request to the specified API endpoint with the search query and other parameters
  const response = await fetch("https://api.tavily.com/search", {
    method: "POST", // HTTP method
    headers: {
      "Content-Type": "application/json", // Specify the request content type as JSON
    },
    body: JSON.stringify({
      api_key: process.env.TAVILY_API_KEY, // API key from environment variables
      query, // The search query passed to the function
      search_depth: "basic", // Search depth parameter
      include_answer: true, // Include the answer in the response
      include_images: false, // Do not include images in the response
      include_raw_content: false, // Do not include raw content in the response
      max_results: 20, // Limit the number of results to 20
    }),
  });

  // Parse the JSON response from the API
  const responseJson = await response.json();

  // Instantiate the OpenAI class
  const openai = new OpenAI();

  // Use the OpenAI API to create a completion based on the JSON response
  const completion = await openai.chat.completions.create({
    messages: [
      {
        role: "system", // Set the role of the message to system
        content: `Summarize the following JSON to answer the research query \`"${query}"\`: ${JSON.stringify(
          responseJson
        )} in plain English.`, // Provide the JSON response to be summarized
      },
    ],
    model: process.env.OPENAI_MODEL || "gpt-4", // Specify the OpenAI model, defaulting to GPT-4 if not set in environment variables
  });

  // Return the content of the first message choice from the completion response
  return completion.choices[0].message.content;
}

```

Next, create a file called `route.ts` in the `copilotkit` folder, and add the following code. The code sets up a scraping action using the CopilotKit framework to fetch and summarize content based on a given link. 

Then it defines an action that calls the scrape function and returns the result. If the required API key is available, it adds this action to the CopilotKit runtime and responds to a POST request using the OpenAI model specified in the environment variables.

```jsx
// Import necessary modules and functions
import { CopilotRuntime, OpenAIAdapter } from "@copilotkit/backend";
import { Action } from "@copilotkit/shared";
import { scrape } from "./tavily"; // Import the previously defined scrape function

// Define a scraping action with its name, description, parameters, and handler function
const scrapingAction: Action<any> = {
  name: "scrapeContent", // Name of the action
  description: "Call this function to scrape content from a url in a query.", // Description of the action
  parameters: [
    {
      name: "query", // Name of the parameter
      type: "string", // Type of the parameter
      description:
        "The query for scraping content. 5 characters or longer. Might be multiple words", // Description of the parameter
    },
  ],
  // Handler function to execute when the action is called
  handler: async ({ query }) => {
    console.log("Scraping query: ", query); // Log the query to the console
    const result = await scrape(query); // Call the scrape function with the query and await the result
    console.log("Scraping result: ", result); // Log the result to the console
    return result; // Return the result
  },
};

// Define an asynchronous POST function to handle POST requests
export async function POST(req: Request): Promise<Response> {
  const actions: Action<any>[] = []; // Initialize an empty array to store actions
  // Check if the TAVILY_API_KEY environment variable is set
  if (process.env["TAVILY_API_KEY"]) {
    actions.push(scrapingAction); // Add the scraping action to the actions array
  }
  // Create a new instance of CopilotRuntime with the defined actions
  const copilotKit = new CopilotRuntime({
    actions: actions,
  });

  const openaiModel = process.env["OPENAI_MODEL"]; // Get the OpenAI model from environment variables

  // Return the response from CopilotKit, using the OpenAIAdapter with the specified model
  return copilotKit.response(req, new OpenAIAdapter({ model: openaiModel }));
}

```

## How To Generate Resume and Cover Letter

Now go to the in-app chatbot you integrated earlier and add a LinkedIn, GitHub or X profile link then press enter.

Once you have added the link, the chatbot will use LangChain and Tavily to scrape content from link profile. Then it will use the content to generate a Resume and Cover Letter.

The resume generated should look as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ey6p8xsgxf3poigz2rko.png)

The cover letter generated should look as shown below.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/39h4te778e6fuahqfv2k.png)

Congratulations! You’ve completed the project for this tutorial.

## Conclusion

You can now build an awesome AI-powered resume generator to both hone in your AI building skills and simplify your job search process!

Please remember to like & save this article if you enjoyed it and let me know what topics you'd want to see covered next.