Create a Tarot View App with the Gemini API in just 15 minutes.
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):
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.
- Resources
To get the UI of your Tarot cards up Dribbble search, DriBBble is the website that the designer visits for ideas.
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:
[
{
"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
- 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 refreshing 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
- 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.
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 > Environment > Add variable
NEXT_PUBLIC_GEMINI_API_KEY
and corresponding value)
- Select
Details: Deloy Vercel For Github
Demo
Access tarot1.bunhere.com for the demo. Official edition at tarot.bunhere.com
View full source code here.
End.
Happy coding!!! ๐ฉ๐ผโ๐ป
References:
Author: bunhere.com
I am always looking for feedback on my writing, so please let me know what you think. โค๏ธ
Subscribe to my newsletter
Read articles from Emma Ngo directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by