Skip to content

Latest commit

 

History

28 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lore of Legends — A League of Legends Chronicle

Live Demo Backend API API Docs Python React

A full-stack interactive story generator set in the world of Runeterra — the universe of League of Legends. Users choose a realm and navigate branching narratives generated by an LLM acting as a Chronicler of Runeterra. Powered by Google Gemini via LangChain, built with FastAPI and React.

Features

  • Runeterra-Immersive Stories — Every tale is set in the world of Runeterra, featuring its regions, champions, factions, and magic system. The LLM is prompted as a Chronicler of Runeterra to deliver lore-rich narratives.
  • Realm-Based Generation — Enter any realm of Runeterra (e.g., Piltover, Demacia, Shadow Isles) and get a unique branching story steeped in that region's lore.
  • Branching Narratives — Each non-ending node offers 2–3 meaningful choices, creating a tree structure 3–4 levels deep with consequences that feel real.
  • Multiple Endings — Stories include both victorious and tragic endings across paths of varying lengths.
  • Async Job Processing — Long-running LLM inference runs in the background via FastAPI's BackgroundTasks; the frontend polls for status in real time.
  • LLM-Powered — Uses Google Gemini (via LangChain) with Pydantic-validated structured output to guarantee deterministic, tree-shaped JSON.
  • League of Legends Design System — A custom LoL-inspired UI with deep viridian backgrounds, gilded gold accents, and steel-trimmed panels. Features glassmorphism cards, grid overlays reminiscent of the LoL client, and a hextech-themed spinner.

Deployments

Service URL
Frontend (Vercel) https://lore-of-legends-pi.vercel.app/
Backend API (Render) https://lore-of-legends.onrender.com
API Docs (Swagger UI) https://lore-of-legends.onrender.com/docs

Tech Stack

Backend (backend/)

Technology Purpose
FastAPI Web framework & REST API
PostgreSQL Relational database
SQLAlchemy ORM & database models
LangChain LLM orchestration framework
LangChain Google GenAI Google Gemini integration
Pydantic Data validation & structured output parsing
Uvicorn ASGI server

Frontend (frontend/)

Technology Purpose
React 19 UI library
Vite Build tool & dev server
React Router v7 Client-side routing
Axios HTTP client for API calls
Cinzel Display & heading typeface
Plus Jakarta Sans Body typeface

LLM

  • Google Gemini (gemini-2.5-flash-lite) via LangChain

Project Structure

lore-of-legends/
├── backend/
│   ├── main.py                    # FastAPI app entry point
│   ├── pyproject.toml             # Python project config & dependencies
│   ├── requirements.txt           # pip dependencies
│   ├── core/
│   │   ├── config.py              # Settings / environment config
│   │   ├── models.py              # Pydantic models for LLM output parsing
│   │   ├── prompts.py             # LLM system prompts (Runeterra storyteller)
│   │   └── story_generator.py     # Story generation logic (LangChain chain)
│   ├── db/
│   │   └── database.py            # SQLAlchemy engine & session setup
│   ├── models/
│   │   ├── story.py               # Story & StoryNode ORM models
│   │   └── job.py                 # StoryJob ORM model (async processing)
│   ├── routers/
│   │   ├── story.py               # /api/stories/ endpoints
│   │   └── job.py                 # /api/jobs/  endpoints
│   └── schemas/
│       ├── story.py               # Pydantic schemas for story API
│       └── job.py                 # Pydantic schemas for job API
├── frontend/
│   ├── index.html                 # HTML entry point (Runeterra-themed)
│   ├── vite.config.js             # Vite configuration
│   ├── package.json               # Node dependencies
│   └── src/
│       ├── main.jsx               # React entry point
│       ├── App.jsx                # Root component with router & Runeterra branding
│       ├── App.css                # LoL-inspired dark fantasy design system
│       ├── index.css              # Reset & base styles (dark viridian palette)
│       ├── util.js                # API base URL helper
│       └── components/
│           ├── StoryGenerator.jsx # Realm input & job polling logic
│           ├── StoryLoader.jsx    # Fetches and loads a completed chronicle
│           ├── StoryGame.jsx      # Interactive branching narrative UI
│           ├── ThemeInput.jsx     # Realm submission form with Runeterra regions
│           └── LoadingStatus.jsx  # Hextech spinner while legend is forged
└── README.md

Getting Started

Prerequisites

  • Python 3.14+
  • Node.js 20+ and npm
  • PostgreSQL instance (local or cloud)
  • Google Gemini API key (get one from Google AI Studio)

Backend Setup

  1. Clone the repository

    git clone https://github.com/jnrgxx/lore-of-legends.git
    cd lore-of-legends/backend
  2. Create and activate a virtual environment

    python -m venv venv
    # Windows:
    venv\Scripts\activate
    # macOS / Linux:
    source venv/bin/activate
  3. Install dependencies

    pip install -r requirements.txt
  4. Configure environment variables

    Create a .env file in backend/:

    DATABASE_URL=postgresql://user:password@localhost:5432/lore_of_legends
    API_KEY=your_gemini_api_key_here
    ALLOWED_ORIGINS=http://localhost:5173
  5. Run the server

    uvicorn main:app --reload

    The API will be available at http://localhost:8000 and interactive docs at http://localhost:8000/docs.

Frontend Setup

  1. Navigate to the frontend directory

    cd lore-of-legends/frontend
  2. Install dependencies

    npm install
  3. Configure API URL (optional)

    Create a .env file in frontend/:

    VITE_API_URL=http://localhost:8000
  4. Start the dev server

    npm run dev

    The app will be available at http://localhost:5173.

Tip: Run both the backend and frontend simultaneously in separate terminals for the full experience.

Design System

The UI is inspired by the League of Legends client:

  • Palette: Deep viridian backgrounds (#0A0E0C), gilded gold accents (#C89B3C — Riot's official gold), steel blue-gray secondary tones (#5B7A6E)
  • Typography: Cinzel Decorative for display text, Cinzel for headings, Plus Jakarta Sans for body copy
  • Effects: Glassmorphism cards with backdrop blur, subtle grid overlay reminiscent of the LoL client, gold line dividers, runic decorative corner elements
  • Animations: Fade-in entrance animations on all panels, animated gold gradient buttons, options that slide on hover

API Endpoints

POST /api/stories/create

Create a new story generation job.

Request body:

{
  "theme": "Piltover"
}

Response (immediate — job queued):

{
  "job_id": "uuid-string",
  "status": "pending",
  "created_at": "2026-05-07T12:00:00Z"
}

GET /api/jobs/{job_id}

Poll for job status.

Response:

{
  "job_id": "uuid-string",
  "status": "completed",
  "story_id": 42,
  "created_at": "2026-05-07T12:00:00Z",
  "completed_at": "2026-05-07T12:00:15Z"
}

Status values: pending → processing → completed / failed

GET /api/stories/{story_id}/complete

Retrieve the full branching story tree.

Response:

{
  "id": 42,
  "title": "Shadows of Piltover",
  "session_id": "uuid-string",
  "created_at": "2026-05-07T12:00:15Z",
  "root_node": {
    "id": 1,
    "content": "The grey glow of hextech lamps flickers across the rain-slicked cobblestones of Piltover's Promenade...",
    "is_ending": false,
    "is_winning_ending": false,
    "options": [
      { "text": "Slip into the back alleys of the Undercity", "node_id": 2 },
      { "text": "Approach the Enforcer with confidence", "node_id": 3 }
    ]
  },
  "all_nodes": { ... }
}

How It Works

  1. User chooses a realm — The React frontend sends a POST /api/stories/create request with a Runeterra region (e.g., "Piltover", "Demacia").
  2. Job is queued — The backend creates a StoryJob record with status pending and returns a job_id immediately.
  3. Background processing — FastAPI's BackgroundTasks calls StoryGenerator.generate_story() in a separate database session.
  4. Chronicler invocation — LangChain sends the realm + structured prompt to Google Gemini, instructing it as a Chronicler of Runeterra to return a complete branching story set in the League of Legends universe.
  5. Pydantic validation — The raw LLM output is parsed and validated against StoryLLMResponse, ensuring deterministic structure.
  6. Database persistence — The story tree is stored as Story and StoryNode records (with options as JSON), forming a recursive graph.
  7. Frontend polling — The React app polls GET /api/jobs/{job_id} every 5 seconds until status is completed.
  8. Story gameplay — Once complete, the frontend fetches the full story tree via GET /api/stories/{story_id}/complete and renders the interactive branching narrative with Runeterra-flavored UI.

License

MIT

Releases

Packages

Contributors

Languages