Add Umami analytics, subscription tiers, tool-call metering

- Analytics: <Analytics /> in root layout loads umami when NEXT_PUBLIC_UMAMI_* env are set
- Subscription: free/pro tiers in Postgres, daily quotas (10msg/5tool free, 1000/500 pro)
- Chat route: checkAndConsume(chat_message) gate before streamText, 429 when exhausted
- Tool metering: getWeather wrapped with withLimit decorator that deducts tool_call quota
- Pricing page at /pricing with upgrade button
- /api/billing/checkout posts to EGBE payment gateway, /api/billing/webhook verifies HMAC and upgrades user on checkout.session.completed
- UsageBadge in chat header polls /api/usage every 15s
- Dropped now-unused entitlements.ts
This commit is contained in:
dmitry.galkin 2026-05-25 17:27:08 +04:00
parent 3e21c2334c
commit 22a172e92d
23 changed files with 1374 additions and 86 deletions

View file

@ -1,38 +1,59 @@
# EGBE Chatbot Template
Next.js chatbot wired to the EGBE LiteLLM proxy. Based on [vercel/chatbot](https://github.com/vercel/chatbot) (Apache 2.0), stripped of Vercel-specific bits and rewired for our infra.
Next.js chatbot wired to the EGBE infra. Based on [vercel/chatbot](https://github.com/vercel/chatbot) (Apache 2.0).
## Stack
## What's wired
- **Next.js 16** (App Router, standalone output)
- **AI SDK** + `@ai-sdk/openai-compatible` → routes to `EGBE_AI_API_URL` (our LiteLLM)
- **NextAuth** (credentials + guest)
- **Drizzle ORM** + Postgres (DB gateway)
- **shadcn/ui** + Tailwind
- **AI** via `@ai-sdk/openai-compatible``EGBE_AI_API_URL` (LiteLLM proxy)
- **Auth** — NextAuth credentials + guest mode
- **DB** — Drizzle ORM + Postgres (provisioned by app-deploy.sh)
- **Analytics** — Umami `<Script>` mounted when `NEXT_PUBLIC_UMAMI_*` env are set
- **Subscriptions** — Free / Pro tiers, daily quotas, tool-call metering, real Stripe checkout via the EGBE payment gateway
## Available models (via LiteLLM)
## Models (via LiteLLM)
`claude-sonnet`, `claude-haiku`, `gpt-5.4`, `gpt-5.4-mini`, `kimi-k2.6`, `minimax-m2.7`, `qwen3-coder`.
`gpt-5.4-mini` (default), `gpt-5.4`, `claude-sonnet`, `claude-haiku`, `kimi-k2.6`, `minimax-m2.7`, `qwen3-coder`. Edit `lib/ai/models.ts`.
Edit `lib/ai/models.ts` to add or remove.
## Subscription model
Two tiers (see `lib/subscription/config.ts`):
| Tier | Messages / day | Tool calls / day |
|------|----------------|------------------|
| Free | 10 | 5 |
| Pro | 1000 | 500 |
- **Message limit** — enforced in `app/(chat)/api/chat/route.ts` via `checkAndConsume(userId, "chat_message")` before `streamText`.
- **Tool limit**`getWeather` is wrapped with `withLimit(tool, "tool_call", userId)` (see `lib/ai/tools/with-limit.ts`). Every tool call deducts 1 unit; if quota is hit the tool returns `{ error: "limit_exceeded", ... }` which the model sees and explains to the user.
- **Quota badge**`<UsageBadge />` in the chat header polls `/api/usage` every 15s.
- **Upgrade flow**`/pricing` → POST `/api/billing/checkout` → redirect to Stripe → webhook at `/api/billing/webhook` flips the user's tier on `checkout.session.completed`.
To add a new tool with metering, just wrap it the same way:
```ts
const meteredFoo = withLimit(myFooTool, "tool_call", session.user.id);
```
To add a new limit kind, edit `lib/subscription/config.ts` (`LimitKey`, `LIMITS`) and `lib/subscription/service.ts`.
## Deploy (from inside an OpenClaw instance)
```bash
${SKILLS_DIR}/scripts/app-deploy.sh <slug> chatbot-template
# 1. provision Umami site (optional — chatbot works without analytics)
${SKILLS_DIR}/../analytics/scripts/analytics-site-create.sh chatbot chatbot.${EGBE_INSTANCE_PUBLIC_ID}.p.egbe.app "Chatbot"
# 2. (optional) create a Stripe product + price for the Pro plan
# Without PRO_STRIPE_PRICE_ID the upgrade button returns 501.
curl -s -H "Authorization: Bearer ${EGBE_GATEWAYS_TOKEN}" -H "Content-Type: application/json" \
-d '{"name":"Chatbot Pro"}' "${EGBE_PAYMENT_GATEWAY_URL}/v1/products"
# then create a price referencing the returned product id, copy the price id
# 3. deploy
APP_ENV_JSON='{"AUTH_SECRET":"'$(openssl rand -base64 32)'","PRO_STRIPE_PRICE_ID":"price_..."}' \
${SKILLS_DIR}/scripts/app-deploy.sh chatbot chatbot-template
```
`app-deploy.sh` auto-injects:
- `DATABASE_URL` (per-app Postgres)
- `EGBE_AI_API_URL`, `EGBE_AI_API_KEY` (instance LiteLLM key)
- `NEXT_PUBLIC_BASE_URL` (the deployed `*.p.egbe.app` host)
You also need `AUTH_SECRET` — set it via `APP_ENV_JSON`:
```bash
APP_ENV_JSON='{"AUTH_SECRET":"'$(openssl rand -base64 32)'"}' \
${SKILLS_DIR}/scripts/app-deploy.sh mychat chatbot-template
```
`app-deploy.sh` auto-injects: `DATABASE_URL`, `EGBE_AI_API_*`, `NEXT_PUBLIC_BASE_URL`, `NEXT_PUBLIC_UMAMI_*`, `EGBE_PAYMENT_GATEWAY_*`, `EGBE_GATEWAYS_TOKEN`. You pass `AUTH_SECRET` and `PRO_STRIPE_PRICE_ID` (if billing is desired) via `APP_ENV_JSON`.
## Local dev
@ -44,14 +65,6 @@ pnpm db:migrate
pnpm dev
```
## Tracking upstream
Pull new vercel/chatbot features manually — `origin/upstream` is not configured by default here. Add it back with:
```bash
git remote add upstream https://github.com/vercel/chatbot.git
```
## License
Apache 2.0 — see `LICENSE`. Derivative work of Vercel, Inc.'s [chatbot](https://github.com/vercel/chatbot).
Apache 2.0 — derivative work of Vercel, Inc.'s [chatbot](https://github.com/vercel/chatbot).