Skip to content Skip to sidebar Skip to footer

Create an AI-Powered Flashcard Application


Flashcards have long been used as an effective tool for learning by providing quick, repeatable questions that help users memorize facts or concepts. Traditionally, flashcards contain a question on one side and the answer on the other. The concept is simple, yet powerful for retention, whether you’re learning languages, mathematics, or any subject.

An AI-powered flashcard game takes this learning method to the next level. Rather than relying on static content, AI dynamically generates new questions and answers based on user input, learning patterns, and performance over time. This personalization makes the learning process more interactive and adaptive, providing questions that target specific areas where the user needs improvement.

In this tutorial, we’ll use LLaMA 3.1, a powerful open-source large language model, to create dynamic flashcards. The AI engine will generate new questions and answers in real time based on the subject matter or keywords the user provides. This enhances the learning experience by making the flashcards more versatile, personalized, and efficient.

Setting Up the Environment for Development

We need to set up our working environment before we start writing code for our flashcard app.

1. Install Node.js and npm

The first step is to install Node.js and npm. Go to the Node.js website and get the Long-Term Support version for your computer’s running system. Follow the steps given for installation.

2. Making a Project With Next.js

Start up your terminal and go to the location where you want to make your project. After that, run these commands:

  • npx create-next-app@latest flash-card-app (With the @latest flag, npm gets the most recent version of the Next.js starting setup.)
  • cd flash-card-app 

It will make a new Next.js project and take you to its path. You’ll be given a number of configuration choices during the setup process, set them as given below:

  • Would you like to use TypeScript? No
  • Would you like to use ESLint? Yes
  • Would you like to use Tailwind CSS? No
  • Would you like to use the src/ directory? No
  • Would you like to use App Router? Yes
  • Would you like to customize the default import alias? No

3. Installing Firebase and Material-UI

In the directory of your project, execute the following command: npm install @mui/material @emotion/react @emotion/styled firebase

Setting Up Firebase

  • Launch a new project on the Firebase Console.
  • Click “Add app” after your project has been built, then choose the web platform (>).
  • Give your app a name when you register it, such as “flash-card-app”.
  • Make a copy of the Firebase setup file. Afterwards, this will be useful.

4. Create a Firebase Configuration File

Make a new file called firebase.js in the root directory of your project and add the following code, replacing the placeholders with the real Firebase settings for your project:

import { initializeApp } from "firebase/app";
import { getAnalytics } from "firebase/analytics";
import { getAuth } from "firebase/auth";
import { getFirestore } from "firebase/firestore";

const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "YOUR_PROJECT_ID.firebaseapp.com",
projectId: "YOUR_PROJECT_ID",
storageBucket: "YOUR_PROJECT_ID.appspot.com",
messagingSenderId: "YOUR_MESSAGING_SENDER_ID",
appId: "YOUR_APP_ID"
 };

const app = initializeApp(firebaseConfig);
const analytics = getAnalytics(app);
export const auth = getAuth(app);
export const db = getFirestore(app);

How to Create an API Token in OpenRouter

We will use the free version of LLaMA 3.1 from OpenRouter and for that, we need to get the API token. Below are the steps to get one:

Step 1: Sign Up or Log In to OpenRouter

  1. Visit OpenRouter’s official website.
  2. Create an account if you don’t have one. You can either sign up with your email or use an OAuth provider like Google, GitHub, or others.
  3. Log in to your OpenRouter account if you already have one.

Step 2: Navigate to API Key Settings

  1. Once you are logged in, go to the Dashboard.
  2. In the dashboard, look for the API or Developer Tools section.
  3. Click on the API Keys or Tokens option.

Step 3: Generate a New API Key

  1. In the API Keys section, you should see a button or link to Generate New API Key.
  2. Click on the Generate button to create a new API key.
  3. You may be asked to give your API key a name. This helps you organize your keys if you have multiple API keys for different projects (e.g., “Flashcard App Key”).

Step 4: Copy the API Key

  1. Once the API key is generated, it will be displayed on the screen. Copy the API key immediately, as some services may not show it again after you leave the page.
  2. Store the API key securely in your environment configuration file (e.g., .env.local).

Step 5: Add API Key to .env.local File

  1. In your Next.js project, open the .env.local file (if you don’t have one, create it).
  2. Add the following line: OPENROUTER_API_KEY=your-generated-api-key-here.

Make sure to replace your-generated-api-key-here with the actual API key you copied.

Step 6: Use the API Key in Your Application

Building the Core Logic to Import LLaMa 3.1 for Creating Flashcards

Create a new file under the app folder with the name route.js and follow the code given below:

import { NextResponse } from "next/server";

const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY;

const systemPrompt = `
You are an AI flashcard creator. Your task is to generate concise and effective flashcards based on the given topic or content. Follow these guidelines:
1. Create clear and concise questions for the front of the flashcard.
2. Provide accurate and informative answers for the back of the flashcard, ensuring they do not exceed one or two sentences.
3. Ensure that each flashcard focuses on a single concept or piece of information.
4. Use simple language to make the flashcards accessible to a wide range of learners.
5. Include a variety of question types, such as definitions, examples, comparisons, and applications.
6. Avoid overly complex or ambiguous phrasing in both questions and answers.
7. When appropriate, use mnemonics or memory aids to help reinforce the information.
8. Tailor the difficulty level of the flashcards to the user's specified preferences.
9. If given a body of text, extract the most important and relevant information for the flashcards.
10. Aim to create a balanced set of flashcards that covers the topic comprehensively.
11. Only generate 10 flashcards.

Return in the following JSON format:
{
    "flashcards": [{
        "front": str,
        "back": str
    }]
}

Remember, the goal is to facilitate effective learning and retention of information through these flashcards.
`;

export async function POST(req) {
    const data = await req.text(); // Get the raw text from the request
  
    try {
      const response = await fetch("https://openrouter.ai/api/v1/chat/completions", {
        method: "POST",
        headers: {
          "Authorization": `Bearer ${OPENROUTER_API_KEY}`,
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          model: "meta-llama/llama-3.1-8b-instruct",
          messages: [
            { role: "system", content: systemPrompt },
            { role: "user", content: data }
          ],
        })
      });
  
      if (!response.ok) {
        throw new Error(`Failed to fetch from OpenRouter AI: ${response.statusText}`);
      }
  
      const completion = await response.json();
      // Extracting JSON from the response content
      const rawJson = completion.choices[0].message.content;
      const startIndex = rawJson.indexOf('{');
      const endIndex = rawJson.lastIndexOf('}') + 1;
      const jsonString = rawJson.substring(startIndex, endIndex);
      const flashcardsData = JSON.parse(jsonString);
  
      // Assuming flashcardsData contains the "flashcards" array directly
      return NextResponse.json({ flashcards: flashcardsData.flashcards });
    } catch (error) {
      console.error("Error processing request:", error);
      return new Response("Error processing request", { status: 500 });
    }
  }

The code works by receiving a POST request from the client and extracting the raw text input using req.text(). It then sends a POST request to the OpenRouter API with a system prompt that outlines how LLaMA 3.1 should generate the flashcards. The response, containing the flashcards in JSON format, is parsed and returned to the client. In case of an error during the API call or processing, the error is logged, and a 500 response is returned to the client.

Building the Core Components for the Flash Card Application Sign In and Sign Up Using Clerk

Step 1: Set Up Your Clerk Account

  1. Sign up for Clerk: Go to Clerk.dev and create an account if you don’t already have one.
  2. Create an application:
    • Once logged in, navigate to the Clerk Dashboard and create a new application.
    • This application will be used for your flashcard app’s authentication system.
  3. Retrieve API keys: In your Clerk dashboard, you will find two keys: Frontend API Key and Secret Key. You will use these in your Next.js project for Clerk integration.

Step 2: Install Clerk SDK in Your Next.js Project

Run the following command to install Clerk’s Next.js SDK: npm install @clerk/nextjs.

Step 3: Set Up Environment Variables

To securely store your Clerk credentials, add them to your .env.local file. Create this file if it doesn’t exist:

NEXT_PUBLIC_CLERK_FRONTEND_API=your-frontend-api-key
CLERK_API_KEY=your-secret-api-key

Replace your-frontend-api-key and your-secret-api-key with the actual values from the Clerk dashboard.

Step 4: Building Sign-In Components




Learn in a Flash






Login to Your Account


);
}” data-lang=”text/javascript”>

"use client";

import { AppBar, Container, Typography, Box, Toolbar, Button } from "@mui/material";
import { useRouter } from 'next/navigation';
import { SignIn } from "@clerk/nextjs";

export default function LoginPage() {
  const router = useRouter();

  const handleHomeClick = () => {
    router.push("https://feeds.dzone.com/");
  };

  return (
    
      
        
          
            Learn in a Flash
          
          
        
      
      
        
          Login to Your Account
        
        
      
    
  );
}

Step 5: Building Sign-Up Components

{
router.push(‘/sign-in’); // Ensure the leading slash for routing
};

return (




Learn in a Flash







Create an Account



);
}
” data-lang=”text/javascript”>

"use client";

import { AppBar, Container, Typography, TextField, Button, Box, Toolbar } from "@mui/material";
import { useRouter } from 'next/navigation';

export default function SignUpPage() {
  const router = useRouter();

  const handleHomeClick = () => {
    router.push("https://feeds.dzone.com/");
  };

  const handleLoginClick = () => {
    router.push('/sign-in'); // Ensure the leading slash for routing
  };

  return (
    
      
        
          
            Learn in a Flash
          
          
          
        
      
      
        
          Create an Account
        
        
      
    
  );
}

Creating Flashcard Generation Frontend Component

1. Setting Up Clerk for User Authentication

In this part, we utilize Clerk’s useUser() hook to manage user authentication. This helps identify whether the user is logged in and provides access to the user’s data, which is crucial for associating flashcards with the correct user.

import { useUser } from "@clerk/nextjs"; 
export default function Generate() { 
const { isLoaded, isSignedIn, user } = useUser(); 
// Other code will be placed below this 
}

Notes:

  • isLoaded: Checks if the user data is fully loaded
  • isSignedIn: Checks if the user is signed in
  • user: Contains the user’s data if they are authenticated

2. Managing Flashcard States

Here, we define the state variables using React’s useState to handle the flashcards, their flipped state, user input, and dialog management for saving the flashcards.

const [flashcards, setFlashcards] = useState([]);  // Stores the generated flashcards
const [flipped, setFlipped] = useState({});  // Keeps track of which flashcards are flipped
const [text, setText] = useState("");  // User input for generating flashcards
const [name, setName] = useState("");  // Name for the flashcard collection
const [open, setOpen] = useState(false);  // Dialog state for saving flashcards

Notes:

  • flashcards: Array to hold generated flashcards
  • flipped: Object to track whether each flashcard is flipped
  • text: Stores the text input from the user to generate flashcards
  • name: Stores the name for the flashcard collection
  • open: Manages the dialog box visibility for saving flashcards

3. Submitting User Input to Generate Flashcards

This function handles sending the input text to an API to generate flashcards and updates the flashcards state based on the API response.

const handleSubmit = async () => {
  try {
    const response = await fetch("/api/generate", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ text }),  // Sends the input text to the API
    });

    if (!response.ok) {
      throw new Error("Failed to fetch flashcards");
    }

    const data = await response.json();  // Extracts the response data
    if (data && data.flashcards) {
      setFlashcards(data.flashcards);  // Updates the flashcards state with the generated flashcards
    }
  } catch (error) {
    console.error("Error generating flashcards:", error);
  }
};

Notes:

  • Sends a POST request to /api/generate with the user’s input text
  • The server returns generated flashcards, which are then set in the flashcards state.

4. Handling Flashcard Flip on Click

This function allows users to click on a flashcard to “flip” it, revealing either the front or back of the card.

const handleCardClick = (index) => {
  setFlipped((prev) => ({
    ...prev,
    [index]: !prev[index],  // Toggles the flipped state of the flashcard at the given index
  }));
};

Notes:

  • When a card is clicked, the flipped state is toggled for the respective card index, switching between showing the front and back.

5. Opening and Closing the Save Dialog

Here, the functions manage the dialog’s visibility. The user can open the dialog to save flashcards and close it when finished.

const handleOpen = () => {
  setOpen(true);  // Opens the dialog
};

const handleClose = () => {
  setOpen(false);  // Closes the dialog
};

Notes:

  • handleOpen: Opens the save dialog box
  • handleClose: Closes the save dialog box

6. Saving Flashcards to Firebase

This function saves the generated flashcards into Firebase Firestore under the current user’s collection, ensuring that each flashcard set is uniquely associated with the user.

const saveFlashcards = async () => {
  if (!name) {
    alert("Please enter a name");
    return;
  }

  const batch = writeBatch(db);  // Firestore batch for atomic writes
  const userDocRef = doc(collection(db, "users"), user.id);  // User document reference
  const docSnap = await getDoc(userDocRef);

  if (docSnap.exists()) {
    const collectionData = docSnap.data().flashcards || [];
    if (collectionData.find((f) => f.name === name)) {
      alert("Flashcard with this name already exists.");
      return;
    } else {
      collectionData.push({ name });  // Add the new flashcard collection name
      batch.set(userDocRef, { flashcards: collectionData }, { merge: true });
    }
  } else {
    batch.set(userDocRef, { flashcards: [{ name }] });  // Create a new user document if it doesn't exist
  }

  const colRef = collection(userDocRef, name);  // Reference to the flashcard collection
  flashcards.forEach((flashcard) => {
    const cardDocRef = doc(colRef);  // Create a document for each flashcard
    batch.set(cardDocRef, flashcard);  // Save each flashcard
  });

  await batch.commit();  // Commit the batch
  handleClose();
  router.push("/flashcards");  // Redirect to the flashcards page after saving
};

Notes:

  • Checks if the user has entered a name for the flashcard collection
  • Uses Firestore batch writes to ensure all flashcards are saved atomically
  • Saves the flashcards under the user’s document and collection in Firestore

7. Rendering the User Interface

This is the main part of the JSX, which handles the form for entering text, displays the flashcards, and renders the save dialog.

Graphic Design> {flashcards.map((flashcard, index) => ( <Grid item xs={12} sm=SEO md=Cybersecurity key={index}> <Card onClick={() => handleCardClick(index)}> <CardActionArea> <CardContent> <Typography variant="h6"> {flipped[index] ? flashcard.back : flashcard.front} </Typography> </CardContent> </CardActionArea> </Card> </Grid> ))} </Grid> <Box sx={{ mt: 4, display: "flex", justifyContent: "center" }}> <Button variant="contained" color="secondary" onClick={handleOpen}> Save </Button> </Box> </Box> )} <Dialog open={open} onClose={handleClose}> <DialogTitle>Save the Flashcards</DialogTitle> <DialogContent> <DialogContentText> Please enter a name for your Flashcard’s Collection </DialogContentText> <TextField autoFocus margin="dense" label="Collection Name" type="text" fullWidth value={name} onChange={(e) => setName(e.target.value)} /> </DialogContent> <DialogActions> <Button onClick={handleClose}>Cancel</Button> <Button onClick={saveFlashcards}>Save</Button> </DialogActions> </Dialog> </Container> ); ” data-lang=”application/typescript”>

return (
  
    
       setText(e.target.value)}  // Update the text state on input
      />
      
    

    {flashcards.length > 0 && (
      
        
          Flashcard Preview
        
        Graphic Design">
          {flashcards.map((flashcard, index) => (
            SEO" md="Cybersecurity" key="{index}">
               handleCardClick(index)}>
                
                  
                    
                      {flipped[index] ? flashcard.back : flashcard.front}
                    
                  
                
              
            
          ))}
        
        
          
        
      
    )}

    
      Save the Flashcards
      
        
          Please enter a name for your Flashcard's Collection
        
         setName(e.target.value)}
        />
      
      
        
        
      
    
  
);

Notes:

  • This renders the form for entering text and generating flashcards.
  • It also handles the rendering of generated flashcards with flip functionality and includes a dialog to save the flashcards to Firebase Firestore.

Sample Look of the Frontend Screen After Creation

Sample Look of the Frontend Screen After Creation

Conclusion

This wraps up the creation of our flashcard application. In this example, I have utilized the LLaMA 3.1 language model, but feel free to experiment with any other model of your choice.

Happy coding!



Source link

Leave a comment

0.0/5

Go to Top