π΄ Introduction
Remember the original Pokedex? It was a handheld encyclopedia that gave you information about every Pokemon you encountered. Fast forward to 2025, and I thought: "What if the Pokedex could actually talk back?"
In this article, I'll take you on a deep technical dive into how I built a "Minimal Pokedex" web app. This isn't just a "hello world" tutorial; I'll show you how I handled real-world challenges like API caching, complex UI states, recursive data structures, and finally, integrating a Large Language Model (LLM) for real-time chat.
Best of all? It's built entirely in Python, hosted for free, and the AI costs $0 to run!
π Live Demo: https://pokedex-ai.streamlit.app/
π¨βπ» GitHub Repo: cam-hm/pokedex-ai
π οΈ The Tech Stack
I chose this stack for speed and simplicity:
- Frontend: Streamlit - Turns Python scripts into shareable web apps in minutes.
- Data: PokeAPI - The gold standard for Pokemon data.
- AI: Groq API - Running Llama-3.3-70b, which is incredibly fast and free for developers.
- Hosting: Streamlit Cloud - One-click deployment from GitHub.
β¨ What We're Building (Feature Overview)
Before we dive into the code, let's look at what this app actually does. It's not just a list of names; it's a full-featured companion.
1. π The Animated Grid
Instead of static images, the home page features a responsive grid of Animated Sprites (using Showdown GIFs). It feels alive and nostalgic.
2. π Deep Detail View
Clicking a Pokemon reveals:
- Official Artwork (High-res)
- Base Stats visualized with colored progress bars.
- Evolution Chain navigation.
- Type Effectiveness calculator.
3. π€ The AI Expert
The killer feature. You can chat with the Pokedex!
- "How do I evolve Eevee into Umbreon?"
- "Is this Pokemon good against Water types?" The AI answers using real game data, not just generic knowledge.
π± Phase 1: The "Monolith" (Building the MVP)
I started exactly how you should start a prototype: One single file (app.py).
My goal was to get something on the screen fast. I didn't worry about folder structures or architecture. I just wanted to fetch Pokemon and show them.
1. Fetching Data (The Naive Way)
I wrote a simple function right inside app.py to hit the PokeAPI. To prevent the app from being slow, I used Streamlit's caching.
# app.py
import streamlit as st
import requests
@st.cache_data
def get_pokemon_list(limit=100):
url = f"https://pokeapi.co/api/v2/pokemon?limit={limit}"
return requests.get(url).json()['results']
pokemon_list = get_pokemon_list()
2. The Grid Layout
I iterated through the list and displayed them. I wanted animated sprites, so I added some logic to check for the "Showdown" GIF first.
# app.py (continued)
cols = st.columns(4)
for i, pokemon in enumerate(pokemon_list):
with cols[i % 4]:
# Logic to find the best sprite URL
sprite_url = get_sprite_url(pokemon['id'])
st.image(sprite_url)
3. Adding Complexity (The Detail View)
Then I added the detail view. This is where things got tricky.
The Tricky Part: Evolution Chains (Recursion!) π€―
Displaying "Charmander β Charmeleon β Charizard" sounds easy, but the API returns a deeply nested JSON tree (chain -> evolves_to -> evolves_to...).
I had to write a recursive function inside app.py to flatten this tree:
# app.py (still the same file!)
def parse_evolution_chain(chain_link, evo_list=None):
if evo_list is None: evo_list = []
species_name = chain_link['species']['name']
evo_list.append(species_name)
# Recursion magic happens here
for evolution in chain_link['evolves_to']:
parse_evolution_chain(evolution, evo_list)
return evo_list
Visualizing Stats
I also wanted those cool colored progress bars for stats (Green for high, Red for low). Streamlit's default bar wasn't enough, so I injected custom HTML/CSS directly into the Python script.
# app.py
st.markdown(f"""
<div style="width: {percentage}%; background-color: {color};"></div>
""", unsafe_allow_html=True)
All of this logicβAPI calls, recursion, HTML generationβwas stuffed into app.py.
By the time I finished the core features, app.py had grown to 500+ lines. It was a mix of everything.
It worked, but it was becoming unmanageable.
ποΈ Phase 2: Refactoring for Scale (The "Spaghetti" Problem)
As I added more featuresβShiny Toggle, Type Effectiveness Modal, Audio Criesβmy app.py started to look like a disaster zone. It was a 500-line monolithic script where UI code was mixed with API logic and state management.
The Problem:
- Readability: I couldn't find anything.
- State Management: Variables were being overwritten.
- Scalability: Adding AI to this mess would be a nightmare.
The Solution: Modular Architecture
I stopped coding features and spent an hour refactoring. I split the app into 4 distinct layers:
pokedex_app/
βββ app.py # Entry point (Main router - only 20 lines!)
βββ src/
β βββ config/ # Constants (Type colors, Gen limits)
β βββ api/ # API Client (Network layer, Caching)
β βββ services/ # Business Logic (Data processing, AI)
β βββ ui/ # Presentation Layer
β βββ components/ # Reusable widgets (Modals, Cards)
β βββ home.py # Home Page Logic
β βββ detail.py # Detail Page Logic
Why this matters:
By moving the Pokemon logic to src/services/pokemon_service.py and the UI to src/ui/, I created a clean slot for the AI. When I was ready to add the chatbot, I didn't have to touch the existing UI codeβI just created a new src/services/ai_service.py.
π€ Phase 3: The AI Brain (Groq + Llama 3)
Now, the killer feature: Conversational AI.
I didn't want a generic chatbot that says "I am an AI model." I wanted a Pokemon Expert that knows exactly which Pokemon you are looking at.
1. The Challenge: Hallucinations
If you ask ChatGPT "How do I beat Charizard?", it gives a generic answer. But what if you're playing a specific ROM hack where Charizard is a Water type? The AI wouldn't know.
2. The Solution: Context Injection (RAG-lite)
I used a technique similar to RAG (Retrieval Augmented Generation). Instead of searching a vector database, I inject the Ground Truth (the exact stats of the current Pokemon) directly into the system prompt.
Here is the exact prompt engineering strategy:
# src/services/ai_service.py
class PokemonChatbot:
def chat(self, pokemon_name, pokemon_data, user_message):
# 1. Extract relevant data
stats = pokemon_data['stats']
types = [t['type']['name'] for t in pokemon_data['types']]
abilities = [a['ability']['name'] for a in pokemon_data['abilities']]
# 2. Construct the Context String
# This is what the AI "sees" before it answers
context = f"""
You are analyzing {pokemon_name}.
Type: {', '.join(types)}
Abilities: {', '.join(abilities)}
Base Stats: {stats}
Height: {pokemon_data['height']} | Weight: {pokemon_data['weight']}
"""
# 3. System Prompt
system_prompt = f"""
You are a Pokemon Expert. Use the data below to answer the user's question.
DATA CONTEXT:
{context}
GUIDELINES:
- Be concise, strategic, and friendly.
- Use emojis (π₯, β‘, π‘οΈ).
- If asked about battles, refer to the specific Stats provided.
- Do NOT make up information not in the data.
"""
# 4. Call Groq API
return self.client.chat.completions.create(
model="llama-3.3-70b-versatile",
messages=[{"role": "system", "content": system_prompt}, ...]
)
3. Why Groq? (Speed is a Feature)
For a chat interface, latency is everything.
- OpenAI GPT-4: Great quality, but can be slow (2-5s) and expensive.
- Groq Llama 3: Instant (<0.5s) and Free for developers.
Groq's LPU (Language Processing Unit) inference engine delivers responses so fast it feels like the AI is pre-generating them. For a real-time companion app, this speed difference is the difference between "cool" and "usable."
π Deployment
directly with GitHub.
The Secret Sauce:
To keep my API keys safe, I used Streamlit's Secrets management.
- Local:
.streamlit/secrets.toml(Git ignored) - Production: Streamlit Dashboard -> App Settings -> Secrets
# .streamlit/secrets.toml
GROQ_API_KEY = "gsk_..."
π‘ Key Takeaways
- UX Matters: Small details like animated sprites, colored stat bars, and loading states make a "toy app" feel professional.
- Architecture Saves Time: Refactoring early allowed me to add the AI feature in just 30 minutes.
- AI Needs Context: The chatbot is only smart because I programmatically feed it the correct data. Without context, it's just ChatGPT; with context, it's a Pokedex Expert.
π Try it out!
Go ahead, ask the AI for a competitive moveset for Magikarp (spoiler: it's Flail).
π App: pokedex-ai.streamlit.app
π Code: github.com/cam-hm/pokedex-ai
If you enjoyed this write-up, drop a star on the repo! β

Top comments (0)