Skip to content

AjayLiu/local-lore

Repository files navigation

LocalLore

Turn geographical searches into historical and cultural discoveries.

Milestone 1

Landing page with debounced location search via the Photon API (OpenStreetMap). Selecting a location opens a full-screen Mapbox GL map centered on that place, with pan and zoom.

Async lore queue (QStash)

When you select a location, the app enqueues a background job instead of calling Gemini on the request thread:

  1. POST /api/lore stores a short-lived job in Upstash Redis and publishes to Upstash QStash.
  2. QStash delivers work to /api/cron/process-lore with flow control (15 requests/minute, 5 parallel max) to protect the Gemini free tier.
  3. The worker fetches Wikipedia articles and runs one generateText call per job.
  4. The map polls GET /api/lore?jobId=… every 2s until pins are ready.

Completed lore jobs are persisted to Supabase (one row per Wikipedia page_id). The map loads cached cards in the current viewport as small dots; zoom in to see headlines (zoom ≥ 14). Supabase is optional for local dev without env vars.

API quota: Each location search uses one Gemini generateText call (Wikipedia is fetched separately, no AI) on LORE_MODEL_ID (default gemini-3.1-flash-lite). On 429 / rate limit, the job stays on that model and QStash retries later. On 503 / high demand, the worker retries with backoff, then tries LORE_MODEL_FALLBACK_IDS in order (gemini-2.5-flash, then gemini-2.5-flash-lite by default). 403 errors fail immediately (check API key / billing).

Prerequisites

Local development

  1. Install dependencies:

    npm install
  2. Copy the environment template and fill in values:

    cp .env.example .env.local

    Required in .env.local:

    • NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN
    • GOOGLE_GENERATIVE_AI_API_KEY
    • QSTASH_TOKEN, QSTASH_CURRENT_SIGNING_KEY, QSTASH_NEXT_SIGNING_KEY
    • UPSTASH_REDIS_REST_URL, UPSTASH_REDIS_REST_TOKEN
    • LORE_APP_URL — your deployed app URL (e.g. https://your-app.vercel.app)
    • Optional LORE_APP_URL_LOCAL — only if you need a non-default local callback port
    • Optional NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY — geographic cache (see below)
    • Optional NEXT_PUBLIC_GA_MEASUREMENT_ID — Google Analytics 4 (e.g. G-XXXXXXXXXX)

Supabase geographic cache

  1. Create a Supabase project and enable the PostGIS extension (Database → Extensions).
  2. Run the SQL migrations in supabase/migrations/ in order in the SQL editor (001_lore_cards.sql for cached cards; 002_lore_searches.sql for community search counts; 003_lore_gemini_logs.sql for Gemini failure/fallback logs).
  3. Add NEXT_PUBLIC_SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY (service role, server-only) to .env.local and Vercel.
  4. After a lore search completes, rows appear in lore_cards. Pan the map to load cached pins via GET /api/lore/cached?west=&south=&east=&north=. When gemini-3.1-flash-lite fails or a fallback runs, rows are appended to lore_gemini_logs (query in the Supabase SQL editor or Table Editor).

Re-searching the same Wikipedia article updates the existing row (page_id is unique), it does not duplicate.

  1. QStash local dev: In a second terminal, run the QStash CLI:

    npx @upstash/qstash-cli dev

    Copy its QSTASH_URL, QSTASH_TOKEN, and signing keys into .env.local. When QSTASH_URL points at 127.0.0.1:8080, the app automatically callbacks to http://127.0.0.1:3000 during npm run dev (even if LORE_APP_URL is your Vercel URL). View messages at the local QStash console, not the cloud dashboard.

  2. In the Mapbox dashboard, restrict your token URLs to:

    • http://localhost:3000/*
  3. Start the dev server:

    npm run dev
  4. Open http://localhost:3000 and search for a location.

Deploy on Vercel

  1. Push this repository to GitHub.
  2. Import the project in Vercel.
  3. Add environment variables from .env.example (including QStash, Redis, and LORE_APP_URL set to https://your-app.vercel.app or rely on VERCEL_URL).
  4. Deploy. After the first deploy, add your production URL to Mapbox token URL restrictions.
  5. Verify search → map → lore pins after the queue processes.

No extra vercel.json configuration is required; Vercel auto-detects Next.js.

Scripts

Command Description
npm run dev Start development server
npm run build Production build
npm run start Run production server
npm run lint Run ESLint

Project structure

src/
  app/
    api/lore/              # Enqueue + poll lore jobs
    api/lore/cached/       # Bbox query for cached lore cards
    api/cron/process-lore/ # QStash worker (Gemini synthesis + Supabase upsert)
  components/              # UI components
  hooks/                   # Shared React hooks
  lib/
    jobs/                  # Redis job store
    lore/                  # Lore agent schema, synthesis, Supabase cache
    supabase/              # Supabase server client
    mapbox/                # Mapbox GL token, cached lore layers
supabase/migrations/       # PostGIS schema for lore_cards
    photon/                # Photon geocoding API client
    qstash/                # QStash publish client
    types/                 # Shared TypeScript types
    wikipedia/             # Wikipedia nearcoord search + article enrichment

What's next

  • Geographic search cache (skip Gemini when an area already has enough cached cards)
  • Supabase Realtime instead of polling (optional)

About

AI-powered local history explorer

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages