# Create a Tarot View App with the Gemini API in just 15 minutes.


![Tarot with Gemini API](https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/tarot-with-gemini.png)

It's a little old, but I still believe I'm GenZ, and when it comes to trouble, GenZ always finds answers from the universe 🤓.

I was introduced to a friend to watch the Tarot, but because he was waiting too long to watch, I happened to see a prompt to watch Tartot on Tiktok. I tried through both ChatGPT and Gemini and found output on both sides attributed to a problem that we're experiencing 😝.

> Anyone who is too serious can drop this content. All three of you, cosmic energy!

## Ideas

The contents of the prompt are like this:

```
Generate 3 random numbers between 1 and 78. Next, look up the corresponding tarot cards, and their meanings. Finally, put together an overall reading for me, based on the 3 cards. Answer in Vietnamese
```

And here's what we got (it's a little bit different because I added a note to show the results in Vietnamese):

![Tarot result from GeminiAPI](https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/result-from-gemini.png)

> In conclusion, both results suggest that we need to "see and listen to the inner voice".

From the idea that I decided to write an app instead of randomly taking three of the 78 Tarot cards, I showed 78 cards out (of course a random show), and then everyone picked out three cards with three numbers. Then re-adjust the `prompt` call API to get the results from Gemini (because the installation from the GeminiAPI is quite simple) and then return them to the user. 

That way, people can be more proactive in choosing cards.

## Deploy

## Interface

Techstack: I've been using NextJS a lot lately, because it's easy to deploy as well. (Vercel).

First, we need to render the layout of the 78 cards for the user to play.

1. **Resources**

To get the UI of your Tarot cards up [Dribbble](https://dribbble.com/search/78-tarot-cards) search, *DriBBble is the website that the designer visits for ideas.*

![Dribbble](https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/Dribbble-78-tarot-cards.png)

After we download the Tarot set, we choose to upload it and access it via S3 (you can store it right in src).

In the image section, we go through the data section, create a data set of tarot cards in the form of JSON queries.

*Note: You can skip this section and initialise an array from 1-78 for processing, because what we need is three numbers "picked" but we want to show more information about the cards so we create this json data set.*

And of course, we generate data by using Gemini 🤪. I'm using the prompt:

```
Create a data set of objects containing the contents of all tarot cards. The main contents of the objects are the id, the name of the card, the main and reverse meaning and further description of the cards. To help us create additional contents for the card, the src is '/[alias-name-of-the-card].png'.
```

Result:

```json
[
    {
        "id": 0,
        "name": "The Fool",
        "mainMeaning": "Beginnings, naivety, spontaneity, freedom.",
        "reverseMeaning": "Restraint, recklessness, risk.",
        "description": "The Fool represents a new beginning, believing in the future, inexperienced, not knowing what's going to happen, lucky beginners, transformers, and believers in the universe.",
        "src": "/the-fool.png",
        "type": "major-arcana"
    },
    {
        ...
    },
    ...
]
```

> Full source: [cards.json](https://github.com/bunheree/tarot/blob/main/src/data/en-cards.json)

2. **Random Tarot Cards**

Write the function to randomly display the order of the tarot cards.

```ts
import { Card } from "@/types/card";

export function shuffleArray<Card>(array: Card[]): Card[] {
  for (let i = array.length - 1; i > 0; i--) {
    const j = Math.floor(Math.random() * (i + 1));
    [array[i], array[j]] = [array[j], array[i]];
  }
  return array;
}
```
> Suhffle source: [src/common/shuffle.ts](https://github.com/bunheree/tarot/blob/main/src/common/shuffle.ts)

Random display of tarot cards

```tsx
import { useEffect, useState } from "react";
import tarotCards from "@/data/cards.json";
import TarotCard from "@/components/TarotCard";
import { shuffleArray } from "@/common/shuffle";
import { Card } from "@/types/card";

...
const rootCards: Card[] = tarotCards
const [cards, setCards] = useState<Card[]>(rootCards)

useEffect(() => {
    setCards(shuffleArray(rootCards)) // Call shuffleArray when loading
}, [rootCards])

const handleRefresh = () => {
    setCards(shuffleArray(rootCards)) // Call shuffleArray when refreshing pick
}

return (
    <>
        {cards && cards.map((card) => (
            <TarotCard key={card.id} card={card} />
        ))}
        <button onClick={handleRefresh}>Refresh</button>
    </>
);
```

> Page source: [src/app/page.tsx](https://github.com/bunheree/tarot/blob/main/src/app/page.tsx)

### Logic Processing

1. Connect to Gemini API

**Step 1:** Go to [aistudio.google](https://aistadio.google.com/) and create the `API Key`. Add to file `.env`

Details: 

```bash
NEXT_PUBLIC_S3_ENDPOINT=sAIza...ICYp
```

Settings:

```bash
yarn add @google/generative-ai
```

**Step 2:** Edit Prompt

```
With 3 cards: [card], [card 2], and [card 3]. Next, look up the corresponding tarot cards and their meanings. Finally, put together an overall reading for me based on the three cards.
```

**Step 3:** Process `code` to connect to GeminiAPI

You can learn more about using GeminiAPI with [document](https://ai.google.dev/gemini-api/docs/get-started/tutorial?lang=web#set-up-project).

![billing information](https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/gemni-api-billing.png)

**Note:** You need to note the billing information in order to process it accordingly. Because I'm using account FREE, there's a 15 request/minute limit, so I created a request limit API `MAX_CALLS_PER_MINUTE`.

```tsx
'use client'
import { useEffect, useState } from "react"

import tarotCards from "@/data/cards.json"
import { shuffleArray } from "@/common/shuffle"
import { Card } from "@/types/card"

import TarotCard from "@/components/TarotCard"
import DefaultCard from "@/components/DefaultCard"

import { GoogleGenerativeAI } from "@google/generative-ai"

const API_KEY = process.env.NEXT_PUBLIC_GEMINI_API_KEY || ''
const MAX_CALLS_PER_MINUTE = 10

export default function Home() {
  const rootCards: Card[] = tarotCards
  const [cards, setCards] = useState<Card[]>(rootCards)
  const [pickCards, setPickCards] = useState<number[]>([])
  const [prompt, setPrompt] = useState<string>("")
  const [result, setResult] = useState<string>("")
  const [isLoading, setIsLoading] = useState<boolean>(false)
  const [callCount, setCallCount] = useState(0)
  const [timer, setTimer] = useState<NodeJS.Timeout | null>(null)

  useEffect(() => {
    setCards(shuffleArray(rootCards))
  }, [rootCards])

  useEffect(() => {
    if (pickCards.length > 2) { // Pick enough 3 cards
      setPrompt(`With 3 cards: ${pickCards[0]}, ${pickCards[1]}, and ${pickCards[2]}. Next, look up the corresponding tarot cards and their meanings. Finally, put together an overall reading for me based on the three cards.`)
    }
  }, [pickCards, cards])

  const handlePickCard = (cardId: number) => {
    if (pickCards.length > 2) {
      console.log('You only can choose 3 cards')
      return
    }
    setPickCards([...pickCards, cardId])
  }

  const handleResetPick = () => {
    setCards(shuffleArray(rootCards))
    setPickCards([])
    setPrompt("")
    setResult("")
  }

  useEffect(() => {
    if (timer === null) {
      const newTimer = setInterval(() => {
        setCallCount(0)
      }, 60000) // Reset count every 60 seconds
      setTimer(newTimer)

      // Clean up timer on component unmount
      return () => clearInterval(newTimer)
    }
  }, [timer])

  const handleReadCards = async () => {
    if (prompt === "") {
      alert('Please choose all three cards!')
      return
    }

    if (callCount >= MAX_CALLS_PER_MINUTE) {
      alert('System overload! Please wait a minute and then try again.')
      return
    }

    setCallCount(callCount + 1)
    await handleSendPromptToGemini(prompt)
  }

  const handleSendPromptToGemini = async (prompt: string) => {
    setIsLoading(true);
    try {
      const genAI = new GoogleGenerativeAI(API_KEY)
      const model = genAI.getGenerativeModel({ model: "gemini-pro" })

      const result = await model.generateContent(prompt)
      const response = result.response
      const text = response.text()

      setResult(text)
    } catch (error) {
      setResult('Failed to fetch response.')
    }
    setIsLoading(false)
  }

  return (
    <main className="flex min-h-screen flex-col items-center justify-between p-24">
      {isLoading &&
        <p className="pb-8">loading...</p>
      }
      <button className={`bg-black text-white p-4 text-center border hover:bg-white hover:text-black ${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`} disabled={isLoading} onClick={handleResetPick}>Reset</button>
      {result &&
        <p className="pb-8">{result}</p>
      }
      <div className="relative flex flex-wrap">
        {cards && cards.filter((card) => !pickCards.includes(card.id)).map((card: Card) => (
          <div key={card.id} className="relative -ml-20 hover:-mt-4">
            <TarotCard card={card} handlePickCard={handlePickCard} />
          </div>
        ))}
      </div>
      <button className={`bg-black text-white p-4 text-center border hover:bg-white hover:text-black ${isLoading ? 'opacity-50 cursor-not-allowed' : ''}`} disabled={isLoading} onClick={() => handleReadCards()}>Read Cards</button>

      <DefaultCard cards={cards} pickCard={pickCards} />
    </main>
  )
}
```

> Page source: [src/app/page.tsx](https://github.com/bunheree/tarot/blob/main/src/app/page.tsx)

### Deloy

* Create `repo` and push your project to Github
* Log in to `Vercel` if you already have an account; connect to your Github account.
  * Select `New project`
  * Select `repo` from your Github account
  * Deloy project (if failed in Settings > Environment > Add variable `NEXT_PUBLIC_GEMINI_API_KEY` and corresponding value)

**Details**: [Deloy Vercel For Github](https://vercel.com/docs/deployments/git/vercel-for-github)

## Demo

Access [tarot1.bunhere.com](https://tarot1.bunhere.com) for the demo. Official edition at [tarot.bunhere.com](https://tarot.bunhere.com)

View full source code [here](https://github.com/bunheree/tarot/tree/version-1.0).

![Tarot | Bunhere](https://bunhere.s3.amazonaws.com/bunhere-blog-public/images/blogs/tarot-bun.png)

## End.

Happy coding!!! 👩🏼‍💻

References: 
* [GeminiAPI document](https://ai.google.dev/gemini-api/docs/get-started/tutorial?lang=web#set-up-project)
* [Deploying to Vercel](https://vercel.com/docs/deployments/overview)

> Author: [bunhere.com](https://bunhere.com) <br>
> *I am always looking for feedback on my writing, so please let me know what you think.* ❤️
