Skip to content

Repository files navigation

πŸ›Έ Uforia

Ask questions about UFO documents.

A portable, production-ready RAG (Retrieval-Augmented Generation) web app. Upload UFO PDFs and images, and chat with them β€” answers come back grounded in your own files, with citations to the exact source passages.

Built with Next.js 14 (App Router) Β· TypeScript Β· Tailwind CSS Β· Prisma Β· PostgreSQL Β· local embeddings (Transformers.js).


✨ Features

  • Document upload β€” drag-and-drop for PDFs and images.
  • Bulk ingestion CLI β€” pull in official government UAP releases (ODNI, AARO, CIA FOIA…) from a curated manifest, a folder, or a URL (npm run ingest).
  • Text extraction β€” PDF parsing (pdf-parse) + OCR for images (tesseract.js).
  • Local embeddings β€” semantic vectors generated on-device with Transformers.js (all-MiniLM-L6-v2). No external embedding API required, so the app is fully portable.
  • Semantic search β€” cosine-similarity retrieval over stored vectors. Works on any standard PostgreSQL (no special extension needed).
  • Cited answers β€” an LLM composes the answer and cites the passages it used ([1], [2]…).
  • Document library β€” search, filter, reprocess, and delete documents with live status.
  • Settings / health β€” built-in system status page (DB, LLM, embeddings, env validation).
  • Cosmic UI β€” responsive, modern, space-themed interface with a UFO logo.

🧠 How it works

Upload ─▢ Local file storage (uploads/)
            β”‚
            β–Ό
       Extract text  (PDF parse / image OCR)
            β”‚
            β–Ό
       Chunk  ─▢  Embed locally (Transformers.js)  ─▢  Store vectors (Postgres)
                                                            β”‚
Question ─▢ Embed query ─▢ Cosine similarity ─▢ Top-k chunks β”˜
                                                  β”‚
                                                  β–Ό
                                         LLM answer + citations

πŸ“¦ Tech stack

Layer Choice
Framework Next.js 14 (App Router), React 18, TypeScript
Styling Tailwind CSS + shadcn/ui
Database PostgreSQL via Prisma ORM
Storage Local filesystem (uploads/, configurable via UPLOADS_DIR)
Embeddings Transformers.js (Xenova/all-MiniLM-L6-v2) β€” local
Extraction pdf-parse (PDF) Β· tesseract.js (image OCR)
LLM Claude API (Anthropic SDK) β€” claude-opus-4-8

βœ… Prerequisites

  • Node.js 18+ (20+ recommended) and Yarn (or npm)
  • A PostgreSQL database (local, Docker, or hosted)
  • (Optional) An Anthropic API key for LLM answer generation. Without it the app returns extractive answers (the most relevant passages).

The embedding model (~90 MB) is downloaded automatically on first use and cached.


πŸš€ Installation

# 1. Install dependencies
yarn install        # or: npm install

# 2. Configure environment
cp .env.example .env
#   then edit .env with your DATABASE_URL and (optionally) ANTHROPIC_API_KEY

# 3. Set up the database schema
yarn prisma generate
yarn prisma db push      # creates tables from prisma/schema.prisma

# 4. Run the dev server
yarn dev                 # http://localhost:3000

Production build

yarn build
yarn start               # serves the production build on port 3000

πŸ“₯ Ingesting government UAP releases

Uforia ships with a bulk ingestion CLI and a curated manifest of official U.S. government UAP/UFO releases (data/sources.json) β€” including the ODNI Preliminary Assessment (2021), the ODNI annual UAP reports, and the AARO Historical Record Report. Ingested documents go through the same pipeline as UI uploads: store β†’ extract text β†’ chunk β†’ embed.

# Ingest everything in data/sources.json
npm run ingest

# Verify the remote sources are reachable first (no DB writes)
npm run ingest -- --check

# Ingest a folder of PDFs/images you downloaded yourself
npm run ingest -- --dir ~/Downloads/uap-docs

# Ingest a single document by URL
npm run ingest -- --url https://example.gov/report.pdf --name AARO-Report.pdf

# Crawl a web page (and its sub-pages) for PDFs/images and ingest what it finds
npm run ingest -- --page https://www.archives.gov/research/topics/uaps --match "uap|ufo"

Crawling pages instead of listing files

A data/sources.json entry can point at a web page instead of a document β€” the ingester scans the page and its sub-pages (same site only) for PDF/image links and ingests them:

{
  "type": "page",
  "title": "National Archives β€” UAP topic page (crawled)",
  "url": "https://www.archives.gov/research/topics/uaps",
  "depth": 1,
  "match": "uap|ufo|pbb|blue.?book|unidentified"
}
  • depth β€” how many levels of sub-pages to follow (default 1)
  • limit β€” optional max documents per source (default: unlimited)
  • match β€” case-insensitive regex; only follows links matching it (keeps the crawl on-topic and skips site logos/icons)

The crawler is capped at 30 page fetches per source with a politeness delay. npm run ingest -- --check dry-runs the crawl and lists what it would ingest without touching the database.

The crawler extracts href/src links and scans linked .csv/.json data files for document URLs β€” many JS-rendered government pages (e.g. the DoD's https://www.war.gov/UFO/ interactive, whose 1,500+ record index lives in a uap-data.csv) expose their whole document list that way.

ℹ️ Beyond that, the crawler reads static HTML only. Pages that render their document lists purely with JavaScript (e.g. catalog.archives.gov search results) won't expose links to it β€” download those in a browser and use --dir.

⚠️ Government sites often block non-browser clients (dni.gov and media.defense.gov sit behind bot protection). If npm run ingest reports download failures, open the URLs from data/sources.json in a browser, save the PDFs into a folder, and run npm run ingest -- --dir <folder>.

Where to find new releases to add to data/sources.json (or download for --dir):

Source URL
AARO (DoD All-domain Anomaly Resolution Office) reports https://www.aaro.mil/
National Archives UAP Records Collection https://www.archives.gov/research/topics/uaps
CIA FOIA Reading Room β€” UFO collection https://www.cia.gov/readingroom/collection/ufos
FBI Vault β€” UFO files https://vault.fbi.gov/UFO

Ingestion is idempotent β€” documents already in the database (matched by file name) are skipped before they are downloaded, so re-running npm run ingest only pulls documents that are new since the last run. Documents that errored during processing are retried on the next run.

πŸŽ₯ Note: only PDFs and images are supported. Video releases (e.g. the Navy "Tic Tac" footage) can't be ingested; official transcripts or reports about them can.


πŸ—„οΈ Database setup

Uforia uses Prisma with three models (see prisma/schema.prisma):

  • Document β€” uploaded file metadata + processing status + extracted text.
  • DocumentChunk β€” text chunks and their embedding vectors (Float[]).
  • ChatMessage β€” chat history with stored source citations.

Create the schema against your database:

yarn prisma db push          # quick sync (great for getting started)
# or, for tracked migrations:
yarn prisma migrate dev --name init

Scaling note (optional pgvector)

Retrieval computes cosine similarity in application code, which keeps Uforia portable to any Postgres and is plenty fast for thousands of chunks. For very large corpora you can install the pgvector extension and switch DocumentChunk.embedding to a vector column with an in-database <=> query. The app code is structured so this is an isolated change in lib/retrieval.ts.


πŸ” Environment variables

See .env.example for the full annotated list. Summary:

Variable Required Description
DATABASE_URL βœ… PostgreSQL connection string
UPLOADS_DIR – Where uploaded files are stored (default: uploads/ in the project root)
ANTHROPIC_API_KEY – Enables Claude answer generation
CHAT_MODEL – Chat model (default claude-opus-4-8)
EMBEDDING_MODEL – Local embedding model (default Xenova/all-MiniLM-L6-v2)
NEXT_PUBLIC_SITE_URL – Public base URL for social images

The Settings page (/settings) runs a live health check and tells you exactly which variables are missing.


🐳 Run with Docker

The included docker-compose.yml spins up the app plus PostgreSQL so you can run the whole stack locally with one command:

docker compose up --build

Then open the app at http://localhost:3000. Uploaded files persist on the uploads_data volume; the database on db_data.

To build just the app image:

docker build -t uforia .
docker run -p 3000:3000 --env-file .env uforia

☁️ Deployment

Vercel

  1. Push this project to a Git repository and import it into Vercel.
  2. Add the environment variables from .env.example in the Vercel dashboard.
  3. Provision a Postgres database (e.g. Vercel Postgres, Neon, Supabase) and set DATABASE_URL accordingly. Note that file storage is on the local disk, so a host with a persistent filesystem is required (serverless filesystems are ephemeral β€” see the note below).
  4. Set the build command to prisma generate && next build.

Note: image OCR and local embeddings load native/WASM models. For consistently low latency and to avoid serverless cold-start limits, a long-running Node host (Docker, Render, Railway, Fly.io, a VM) is recommended over pure serverless.

Any Node host (Docker / Render / Railway / Fly.io / VM)

yarn install
yarn prisma generate && yarn prisma db push
yarn build
yarn start

πŸ“ Project structure

.
β”œβ”€β”€ app/
β”‚   β”œβ”€β”€ api/
β”‚   β”‚   β”œβ”€β”€ chat/route.ts                 # RAG: retrieve + answer
β”‚   β”‚   β”œβ”€β”€ documents/route.ts            # list documents
β”‚   β”‚   β”œβ”€β”€ documents/upload/route.ts     # upload + kick off processing
β”‚   β”‚   β”œβ”€β”€ documents/[id]/route.ts       # get status / delete
β”‚   β”‚   β”œβ”€β”€ documents/[id]/reprocess/...  # retry processing
β”‚   β”‚   └── health/route.ts               # config/health check
β”‚   β”œβ”€β”€ upload/page.tsx                   # drag-and-drop upload
β”‚   β”œβ”€β”€ documents/page.tsx                # library / management
β”‚   β”œβ”€β”€ chat/page.tsx                     # chat interface
β”‚   β”œβ”€β”€ settings/page.tsx                 # settings & status
β”‚   β”œβ”€β”€ layout.tsx                        # shell, navbar, footer, starfield
β”‚   └── page.tsx                          # landing page
β”œβ”€β”€ components/                           # UI components (logo, navbar, chat, etc.)
β”œβ”€β”€ hooks/use-documents.ts                # documents data + polling
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ db.ts            # Prisma client
β”‚   β”œβ”€β”€ env.ts           # env validation
β”‚   β”œβ”€β”€ storage.ts       # local file storage helpers
β”‚   β”œβ”€β”€ pdf.ts           # PDF text extraction
β”‚   β”œβ”€β”€ ocr.ts           # image OCR
β”‚   β”œβ”€β”€ embeddings.ts    # local embeddings + chunking + cosine similarity
β”‚   β”œβ”€β”€ retrieval.ts     # semantic search
β”‚   β”œβ”€β”€ llm.ts           # answer generation + extractive fallback
β”‚   └── process.ts       # full ingestion pipeline
β”œβ”€β”€ prisma/schema.prisma # Document, DocumentChunk, ChatMessage
β”œβ”€β”€ public/              # logo, favicon, OG image
β”œβ”€β”€ Dockerfile
β”œβ”€β”€ docker-compose.yml
└── .env.example

🧩 Supported file types

  • PDF (application/pdf) β€” text extracted directly.
  • Images (image/png, image/jpeg, image/webp, image/gif, image/bmp, image/tiff) β€” text extracted via OCR.

Scanned/image-only PDFs may yield little text. Upload the pages as images so OCR can run.


πŸ“ License

MIT β€” use it, fork it, explore the unknown. πŸ›Έ

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages