No description
Stripe rejects emails like 'guest-1779719735974' with 400 email_invalid. Falling back to no email lets Stripe collect it on the checkout page itself, which is the standard flow for unauthenticated upgrades. |
||
|---|---|---|
| .vscode | ||
| app | ||
| components | ||
| hooks | ||
| lib | ||
| public | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| biome.jsonc | ||
| components.json | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| drizzle.config.ts | ||
| LICENSE | ||
| next.config.ts | ||
| package.json | ||
| pnpm-lock.yaml | ||
| postcss.config.mjs | ||
| proxy.ts | ||
| README.md | ||
| tsconfig.json | ||
EGBE Chatbot Template
Next.js chatbot wired to the EGBE infra. Based on vercel/chatbot (Apache 2.0).
What's wired
- 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 whenNEXT_PUBLIC_UMAMI_*env are set - Subscriptions — Free / Pro tiers, daily quotas, tool-call metering, real Stripe checkout via the EGBE payment gateway
Models (via LiteLLM)
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.
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.tsviacheckAndConsume(userId, "chat_message")beforestreamText. - Tool limit —
getWeatheris wrapped withwithLimit(tool, "tool_call", userId)(seelib/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/usageevery 15s. - Upgrade flow —
/pricing→ POST/api/billing/checkout→ redirect to Stripe → webhook at/api/billing/webhookflips the user's tier oncheckout.session.completed.
To add a new tool with metering, just wrap it the same way:
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)
# 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, 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
cp .env.example .env.local
# fill in DATABASE_URL, EGBE_AI_API_KEY, AUTH_SECRET
pnpm install
pnpm db:migrate
pnpm dev
License
Apache 2.0 — derivative work of Vercel, Inc.'s chatbot.