A season-aware AI workspace for FIRST Robotics Competition teams.
Live app | Repository | Privacy Policy | Terms | License
Curator is a Next.js 16 app for FRC teams that need grounded answers, source-backed rules help, live event context, and structured team memory in one workspace. It combines a chat interface, retrieval over indexed season documents, optional live web and The Blue Alliance lookups, project organization, public chat sharing, support/reporting tools, a public news feed, and an admin console for operations and content management.
Curator is not affiliated with FIRST. Official manuals, Q&A, awards guidance, event data, and field documentation remain the authority.
- Answers FRC questions with season-aware prompts tuned for rookie and veteran audiences.
- Supports
fast,balanced, anddeepsearch modes for different retrieval depth and latency tradeoffs. - Pulls citations from indexed PDFs, optional live web search, and optional The Blue Alliance tool calls.
- Stores season-scoped and general document chunks in Qdrant and serves source files from MinIO-compatible storage.
- Organizes chats into projects with context summaries and project-level memory.
- Supports guest chats in browser storage, then transfers them into the account after sign-in.
- Lets users publish chats with shareable public links and browse public chat views safely.
- Includes onboarding, default chat/search preferences, theme controls, cookie preferences, data export, and support forms.
- Captures answer-quality feedback, message reports, support requests, and moderation signals.
- Publishes a
/newsfeed backed by admin-managed blog posts. - Ships an admin workspace for stats, documents, news posts, users, chats, reports, feedback, and operational triage.
- Runs as a PWA and includes a Capacitor iOS shell with native Google sign-in support, offline handling, and keyboard/runtime fixes.
| Area | Technology |
|---|---|
| App | Next.js 16 App Router, React 19, TypeScript |
| UI | Tailwind CSS 4, Radix UI, Base UI, lucide-react, Framer Motion |
| Auth | Auth.js / NextAuth with Google OAuth, Apple Sign In, and native iOS Google token sign-in |
| State | Zustand persisted client state for chat UI preferences and guest conversations |
| Database | PostgreSQL with Drizzle ORM and SQL migrations |
| Retrieval | Qdrant vector search plus MinIO-compatible object storage |
| AI | OpenRouter chat, title, and document-description models |
| Search | Indexed FRC docs, optional LangSearch web search, optional The Blue Alliance MCP bridge |
| Observability | Sentry, app logs, client error ingestion, rate limiting |
| Delivery | PWA assets, service worker, metadata, Open Graph, sitemap, robots, IndexNow, Capacitor iOS |
| Tests | Vitest |
Requirements:
- Node.js 20+
- npm
- PostgreSQL
- Qdrant
- Google OAuth credentials
- OpenRouter API key
Optional but commonly used:
- Docker for local Postgres and Qdrant from
docker-compose.yml - MinIO or another S3-compatible object store for document uploads and source-file viewing
- LangSearch API key for live web search
- The Blue Alliance API key for TBA-backed live lookups
Install dependencies:
npm installCreate local environment files:
cp .env.example .env.local
cp .env.example .env.development.localIf you plan to use npm run build, npm run build:ci, or npm start, also create .env because those scripts load that file explicitly:
cp .env.example .envIf you use the included Docker services, make sure your DB settings match the compose port:
DATABASE_URL=postgresql://curator:<your-local-postgres-password>@localhost:5437/curator
POSTGRES_PASSWORD=<your-local-postgres-password>
QDRANT_URL=http://localhost:6333
QDRANT_API_KEY=<your-local-qdrant-api-key>Start local infrastructure:
docker compose --env-file .env.local up -d postgres qdrantThe compose file does not start MinIO. Point the MinIO vars at an existing bucket or run your own S3-compatible store separately if you want document upload and document viewing to work end to end.
Run migrations:
npm run db:migrate:devStart the app:
npm run devOpen http://localhost:3000.
The baseline template lives in .env.example. The app currently references the following groups of variables:
| Group | Variables |
|---|---|
| Core | NEXT_PUBLIC_SITE_URL, AUTH_URL, AUTH_SECRET, DATABASE_URL, PORT |
| Web auth | AUTH_GOOGLE_ID, AUTH_GOOGLE_SECRET, NEXT_PUBLIC_AUTH_GOOGLE_ID, ADMIN_EMAILS |
| Native and Apple auth | AUTH_GOOGLE_IOS_CLIENT_ID, AUTH_APPLE_ID, AUTH_APPLE_SECRET, NEXT_PUBLIC_APPLE_SIGNIN_ENABLED |
| AI | OPENROUTER_API_KEY, OPENROUTER_CHAT_MODELS, OPENROUTER_DESCRIPTION_MODELS, OPENROUTER_TITLE_MODEL |
| Retrieval | QDRANT_URL, QDRANT_COLLECTION, MINIO_ENDPOINT, MINIO_USE_SSL, MINIO_ACCESS_KEY, MINIO_SECRET_KEY, MINIO_BUCKET |
| Optional live search | LANGSEARCH_API_KEY, TBA_MCP_ENABLED, TBA_API_KEY |
| Site and analytics | NEXT_PUBLIC_GOOGLE_ANALYTICS_ID, NEXT_PUBLIC_GOOGLE_SITE_VERIFICATION, NEXT_PUBLIC_APP_BUILD_ID |
| Sentry | NEXT_PUBLIC_SENTRY_DSN, SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_TRACES_SAMPLE_RATE, NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE, NEXT_PUBLIC_SENTRY_REPLAY_SESSION_SAMPLE_RATE, NEXT_PUBLIC_SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE, SENTRY_RELEASE_MANAGEMENT_ENABLED, SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT |
| Indexing | INDEXNOW_KEY, INDEXNOW_SITE_URL, INDEXNOW_SUBMIT_ON_START, INDEXNOW_SUBMIT_DELAY_MS |
| Runtime helpers | PM2_PROCESS_NAME, EVAL_BASE_URL |
Generate an auth secret with:
openssl rand -base64 32For Apple Sign In, generate the client secret separately if you configure that provider:
node scripts/generate-apple-secret.mjs- Empty-state prompt suggestions, season selection, streaming responses, markdown/code rendering, and citations.
- Search modes:
fast: answer from current context and a smaller document pass.balanced: short document, web, and live-tool loop.deep: broader tool loop with larger document searches and more web coverage.
- Optional fact-check pass for answers that should be re-verified against retrieved document context.
- Guest usage is stored locally and currently limited to
3message sends before sign-in is required.
- Google sign-in on web.
- Apple sign-in on web when Apple credentials are configured.
- Native Google sign-in for Capacitor iOS using an ID-token credentials flow.
- Onboarding captures preferred name, team number, chat mode, and search mode.
- Settings support theme selection, default chat mode, default search mode, cookie preferences, account export, and reset controls.
- Projects group chats and maintain a reusable project context summary.
- Public conversation sharing exposes read-only share links for opted-in chats.
- Users can report assistant messages, submit quality feedback, and send support requests from the support form.
- Account export includes user profile data, settings, projects, conversations with messages, support requests, and reports.
/newsrenders published blog posts managed from the admin panel.- Admin routes cover stats, news, documents, users, chats, reports, feedback, and ops.
- Document uploads can be stored in object storage, chunked, embedded, and indexed into Qdrant.
- Admin stats compare database chunk counts with Qdrant vector counts and surface moderation and auth health.
| Command | Purpose |
|---|---|
npm run dev |
Start the Next.js dev server on the default port. |
npm run dev:local |
Start the dev server on port 3001. |
npm run build:next |
Remove .next and build using values loaded from .env. |
npm run build |
Run the production build, then restart the PM2 process. |
npm run build:ci |
Build without the PM2 restart step. |
npm start |
Start the production server through scripts/start-next.js using .env. |
npm run lint |
Run ESLint. |
npm test |
Run the Vitest suite. |
npm run eval:chat |
Run chat evaluation cases against the configured base URL. |
npm run db:generate |
Generate Drizzle migrations. |
npm run db:migrate |
Run Drizzle migrations with the current shell environment. |
npm run db:migrate:dev |
Run Drizzle migrations using .env.development.local. |
npm run db:studio |
Open Drizzle Studio with the current shell environment. |
npm run db:studio:dev |
Open Drizzle Studio using .env.development.local. |
npm run site:check-assets |
Check public SEO and asset endpoints against a target URL. |
npm run indexnow:submit |
Submit key site URLs to IndexNow. |
npm run cap:assets |
Generate Capacitor iOS app icons from resources. |
npm run cap:sync |
Sync web assets and plugins into the Capacitor project. |
npm run cap:open:ios |
Open the iOS workspace in Xcode. |
Build and run:
npm run build
npm startnpm run build assumes a PM2-managed process and calls scripts/restart-curator.mjs. Set PM2_PROCESS_NAME if the deployed process is not named curator.
npm start uses scripts/start-next.js so process managers can pass ports consistently. The same startup path can also submit IndexNow once per process start:
INDEXNOW_SUBMIT_ON_START=true
INDEXNOW_SUBMIT_DELAY_MS=5000Curator exposes public discovery surfaces through:
robots.txtfromapp/robots.tssitemap.xmlfromapp/sitemap.ts- Open Graph and app metadata in
app/andpublic/ llms.txtinpublic/llms.txt- an IndexNow key route at
/indexnow-key.txt
Admin routes and admin APIs are marked noindex, and shared/private conversation boundaries are enforced in route handlers before content is returned.
Google Analytics stays off until the visitor accepts analytics cookies:
NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=G-XXXXXXXXXXSentry supports browser and server errors, tracing, and optional replay sampling:
NEXT_PUBLIC_SENTRY_DSN=https://...
SENTRY_DSN=https://...
SENTRY_ENVIRONMENT=production
SENTRY_TRACES_SAMPLE_RATE=0.1
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=0.1
NEXT_PUBLIC_SENTRY_REPLAY_SESSION_SAMPLE_RATE=0
NEXT_PUBLIC_SENTRY_REPLAY_ON_ERROR_SAMPLE_RATE=0Source map upload only runs when release management is enabled and Sentry build credentials are present:
SENTRY_RELEASE_MANAGEMENT_ENABLED=true
SENTRY_AUTH_TOKEN=sntrys_...
SENTRY_ORG=your-org-slug
SENTRY_PROJECT=your-project-slugThe app also:
- registers a service worker outside local dev
- serves an offline screen at
/offline - attempts asset recovery when stale Next.js chunks fail to load
- shows a slow-connection banner and a native Capacitor offline shell when appropriate
app/ App Router pages, route handlers, metadata, PWA entrypoints
app/(main)/ Main chat and public news routes
app/admin/ Admin workspace routes
app/api/ Chat, account, admin, support, feedback, and document APIs
components/analytics/ Consent banner and Google Analytics loader
components/auth/ Auth, onboarding, and ToS modals
components/chat/ Chat app, window, input, markdown, search activity, citations
components/admin/ Admin panels for docs, chats, reports, users, blog, and ops
components/pwa/ Service-worker runtime and offline UX helpers
components/sidebar/ Main workspace sidebar, projects, history, and dialogs
lib/ AI, retrieval, auth helpers, DB access, logging, rate limiting, SEO
lib/db/ Drizzle schema and SQL migrations
public/ Legal docs, PWA assets, logos, icons, and `llms.txt`
scripts/ Build/runtime helpers, evals, asset checks, IndexNow, Apple secret
tests/ Vitest coverage
docs/superpowers/ Internal design specs and implementation plans
The live legal documents are:
Keep them in sync with product changes. If a feature changes data collection, storage, third-party services, cookies, data retention, authentication, user rights, or acceptable use, update the relevant sections and bump the "Last updated" date in the same change.
Before shipping a change, run the checks that match the touched surface:
npm run lint
npm test
npm run build:ciFor analytics, Sentry, SEO, PWA, or static-asset work, also run:
npm run site:check-assets