bunhere.com
Published on

Create Tarot View App with Gemini API in just 15'?

Medium: Create Tarot View App with Gemini API in just 15'?

Tarot with Gemini API

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 (Here's a little bit different because I added a note to show the results in Vietnamese):

Tarot result from GeminiAPI

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 as deploy. (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 search, DriBBble is the website that the designer visits for ideas.

Dribbble

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 jsons as queries.

Note: You can skip this section and initialize 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, and help us create an additional contents for the card is src is '/[alias-name-of-the-card].png'.

Result:

[
    {
        "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

  1. Random Tarot Cards

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

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

Random display of tarot cards

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 refresh pick
}

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

Page source: src/app/page.tsx

Logic Processing

  1. Connect to Gemini API

Step 1: Go to aistudio.google and create the API Key. Add to file .env

Details:

NEXT_PUBLIC_S3_ENDPOINT=sAIza...ICYp

Settings:

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.

billing information

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.

'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

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 > Enviroment > Add variable NEXT_PUBLIC_GEMINI_API_KEY and corresponding value)

Details: Deloy Vercel For Github

Demo

Access tarot1.bunhere.com for the demo. Official edition at tarot.bunhere.com

View full source code here.

Tarot | Bunhere

End.

Happy coding!!! 👩🏼‍💻

References:

Author: bunhere.com
I am always looking for feedback on my writing, so please let me know what you think. ❤️