Skip to content

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

1001 Nights · 一千零一夜

A bedtime-story generator for kids 2–7. Pick characters from familiar shows, name a theme, and the model spins a short story tuned for read-aloud bedtime — with optional turn-taking, where the listener picks the next direction at each pivot.

Built on Next.js 16 (App Router), Clerk auth, Supabase, Stripe, OpenRouter, msedge-tts / Fish Audio, Groq STT.

Modes

  • Listen mode — one streamed story, ~5 min read-aloud, with TTS that plays sentence-by-sentence as the prose lands.
  • Turns mode — the AI writes a small segment, stops at a pivot, and offers three choices (action / dialogue / event). The listener picks (or types their own line). The next AI segment is steered by what they said: the prompt's cue directive demands the first sentence visibly act on the listener's pick. 4 pivots + 1 closing for short stories.

Both modes share a unified read-aloud control (play/pause + speed dropdown) in the header. Auto-read-aloud while streaming is gated by a global "Sound" preference in the home dropdown.

Tiers

Anonymous Free signed-in Paid ($1.99 / mo or $19.99 / yr)
Stories per session 3 5 / day unlimited
Library cap none 10 saved unlimited
TTS voice Edge Edge Fish Audio (premium)
Story model gpt-oss-120b (free) gpt-oss-120b (free) Claude Sonnet 4.5

Paywall and /subscribe are the same screen — context-appropriate eyebrow ("You've used today's free stories" / "Library is full" / "Upgrade") + unified plan picker.

Stack

  • Runtime: Next.js 16 (App Router, Turbopack), React 19, Bun for dev / build
  • Auth: Clerk (with Svix-signed webhooks for user.created / user.updated)
  • Database: Supabase Postgres + RLS, Storage bucket for TTS cache
  • Billing: Stripe Checkout + Customer Portal + signed webhooks (checkout.session.completed, customer.subscription.*, invoice.payment_failed)
  • Rate limit + counters: Upstash Redis (per-IP rate limit, daily story counter, 60 s subscription cache)
  • LLM: OpenRouter (free tier: openai/gpt-oss-120b:free; paid: anthropic/claude-sonnet-4.5)
  • TTS: msedge-tts (free, via /api/tts) and Fish Audio (paid voices); audio is HTTP-streamed and cached in Supabase Storage
  • STT: Groq Whisper (/api/stt) for the take-turns mic input
  • Moderation: OpenAI moderation (currently soft-disabled in story routes — see app/api/story/route.ts for how to re-enable)
  • Observability: Sentry (browser + server, source-map upload, ad-blocker tunnel route at /monitoring), PostHog (anon + identified events)
  • Cron: Vercel Cron → /api/cron/spend-alert (daily, authed via CRON_SECRET Bearer)

Local development

Prerequisites: Bun, a .env.local with the keys listed in .env.example.

bun install
bun run dev          # http://localhost:3000
bun run build        # production build (also typechecks)

For end-to-end testing of the Stripe webhook locally:

stripe login
stripe listen --forward-to http://localhost:3000/api/webhooks/stripe
# Copy the printed whsec_… into .env.local STRIPE_WEBHOOK_SECRET, restart dev.

The Clerk webhook is not load-bearing for sign-up locally — requireUserId() in lib/auth.ts lazy-creates the Supabase users row on every authenticated request. Wire the Clerk webhook only in prod (it's used for user.updated — email changes etc.).

Project layout

  • app/ — App Router pages + route handlers (app/api/**)
  • components/ — UI components (one per surface; e.g. TakeTurnsMode.tsx, SubscribePanel.tsx, Paywall.tsx, ReadAloudControl.tsx)
  • hooks/ — client hooks (useStoryGenerator, useStoryTurns, useReadAloud, useAutoReadAloud, useBillingState, useUserProfile, useSavedStories, useTTS, useSpeechRecognition, useLanguage)
  • lib/ — shared modules (claude.ts — prompts + OpenRouter streams; tts.ts — TTS speaker logic; billing.ts, auth.ts, storage.ts, storage-server.ts, tts-cache.ts, analytics.ts, etc.)
  • lib/config/site.ts — single source of truth for legal entity name / contact / DMCA mailing address (rendered in /terms and /privacy; the DMCA procedure lives at /terms#dmca)
  • supabase/migrations/ — 0001_init.sql (users, stories, profiles, RLS, atomic free-story RPC) + 0002_billing.sql (subscription columns)
  • proxy.ts — Clerk middleware. Route allowlist for anon-friendly surfaces; protected /api/* routes return JSON 401 instead of HTML redirect (so client fetch() doesn't choke on < from the sign-in page)
  • vercel.json — registers the daily spend-alert cron

Deployment

See DEPLOYMENT.md for the full Vercel + Stripe + Clerk launch sequence. Short version: deploy on *.vercel.app first, wire webhooks, smoke-test the full sign-up → paywall → subscribe → cancel cycle, then promote to a custom domain.

Repo conventions

  • See CLAUDE.md → AGENTS.md for the project conventions agents in this repo follow.
  • Comments in code explain why, not what — read the comment if the code surprises you.
  • Tests live under tests/. (Currently sparse; expand as flows stabilize.)

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages