Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/strict-bushes-change.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"chatbot-with-billing-with-clerk": patch
---

feat(examples): new example with clerk auth
15 changes: 15 additions & 0 deletions examples/chatbot-with-billing-with-clerk/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Clerk keys — https://dashboard.clerk.com
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=****
CLERK_SECRET_KEY=****

# required for non-vercel deployments, vercel uses OIDC automatically
# https://vercel.com/ai-gateway
AI_GATEWAY_API_KEY=****

# https://vercel.com/docs/postgres
POSTGRES_URL=****

# https://polar.sh — billing destination
# POLAR_SERVER can be 'sandbox' (default) or 'production'
POLAR_ACCESS_TOKEN=****
POLAR_SERVER=sandbox
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Lint
on:
push:

jobs:
build:
runs-on: ubuntu-22.04
strategy:
matrix:
node-version: [20]
steps:
- uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: 10.32.1
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: pnpm install --no-frozen-lockfile
- name: Run lint
run: pnpm run lint
- name: Run type check
run: pnpm run check-types
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
name: Playwright Tests
on:
push:
branches: [main, master]
pull_request:
branches: [main, master]

jobs:
test:
timeout-minutes: 30
runs-on: ubuntu-latest
env:
AUTH_SECRET: ${{ secrets.OSS_AUTH_SECRET }}
AI_GATEWAY_API_KEY: ${{ secrets.OSS_AI_GATEWAY_API_KEY }}
POSTGRES_URL: ${{ secrets.OSS_POSTGRES_URL }}
POLAR_ACCESS_TOKEN: ${{ secrets.OSS_POLAR_ACCESS_TOKEN }}
POLAR_SERVER: 'sandbox'

steps:
- uses: actions/checkout@v4
with:
fetch-depth: 1

- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: 10.32.1

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Install dependencies
run: pnpm install --no-frozen-lockfile

- name: Build application
run: pnpm run build

- name: Cache Playwright browsers
uses: actions/cache@v3
id: playwright-cache
with:
path: ~/.cache/ms-playwright
key: ${{ runner.os }}-playwright-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Install Playwright Browsers
if: steps.playwright-cache.outputs.cache-hit != 'true'
run: pnpm run test:setup

- name: Run Playwright tests
run: pnpm run test:e2e

- uses: actions/upload-artifact@v4
if: always() && !cancelled()
with:
name: playwright-report
path: playwright-report/
retention-days: 7
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Pull Upstream Template

on:
schedule:
- cron: '0 0 * * *'
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
pull-and-update:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: 9

- name: Run pull script
run: node scripts/pull-upstream.js

- name: Create Pull Request
uses: peter-evans/create-pull-request@v7
with:
commit-message: 'chore: sync upstream template from narevai/ai-billing'
branch: chore/sync-upstream
delete-branch: true
title: 'chore: sync upstream template from narevai/ai-billing'
body: 'Automated sync of upstream template changes from narevai/ai-billing.'
27 changes: 27 additions & 0 deletions examples/chatbot-with-billing-with-clerk/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.pnpm-store/
node_modules
.pnp
.pnp.js
coverage
.next/
out/
build
.DS_Store
*.pem
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
.env.local
.env.development.local
.env.production.local
.turbo
.env
.vercel
.env*.local
/test-results/
/playwright-report/
/blob-report/
/playwright/*
next-env.d.ts
tsconfig.tsbuildinfo
14 changes: 14 additions & 0 deletions examples/chatbot-with-billing-with-clerk/.oxlintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"$schema": "https://raw.githubusercontent.com/oxc-project/oxc/main/npm/oxlint/configuration_schema.json",
"plugins": ["typescript"],
"categories": {
"correctness": "warn",
"suspicious": "warn"
},
"rules": {
"eslint/no-shadow": "off",
"eslint/no-loss-of-precision": "off",
"eslint/no-unused-vars": "off"
},
"ignorePatterns": ["**/dist/**", "**/coverage/**", "**/.next/**"]
}
69 changes: 69 additions & 0 deletions examples/chatbot-with-billing-with-clerk/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<a href="https://narev.ai/guides">
<img alt="Chatbot with Billing" src="app/(chat)/opengraph-image.png">
<h1 align="center">Chatbot with AI Billing (Clerk)</h1>
</a>

<p align="center">
This is an open-source template built with Next.js, the AI SDK, <strong>Clerk</strong> for authentication, and the <strong>ai-billing</strong> package. It helps you quickly build powerful chatbot applications with built-in monetization and billing capabilities.
</p>

<p align="center">
<a href="https://narev.ai/docs"><strong>Read Docs</strong></a> ·
<a href="#features"><strong>Features</strong></a> ·
<a href="#model-providers"><strong>Model Providers</strong></a> ·
<a href="#deploy-your-own"><strong>Deploy Your Own</strong></a> ·
<a href="#running-locally"><strong>Running locally</strong></a>
</p>
<br/>

## Features

- **[ai-billing](https://narev.ai/docs/sdk/ai-billing)**
- Seamless monetization and billing integration for your AI applications
- Powered by [narev.ai](https://narev.ai)
- [Next.js](https://nextjs.org) App Router
- Advanced routing for seamless navigation and performance
- React Server Components (RSCs) and Server Actions for server-side rendering and increased performance
- [AI SDK](https://ai-sdk.dev/docs/introduction)
- Unified API for generating text, structured objects, and tool calls with LLMs
- Hooks for building dynamic chat and generative user interfaces
- Supports OpenAI, Anthropic, Google, xAI, and other model providers via AI Gateway
- [shadcn/ui](https://ui.shadcn.com)
- Styling with [Tailwind CSS](https://tailwindcss.com)
- Component primitives from [Radix UI](https://radix-ui.com) for accessibility and flexibility
- Data Persistence
- [Neon Serverless Postgres](https://vercel.com/marketplace/neon) for saving chat history and user data
- [Clerk](https://clerk.com)
- Authentication with email verification code (passwordless sign-in)

## Model Providers

This template uses the [Vercel AI Gateway](https://vercel.com/docs/ai-gateway) to access multiple AI models through a unified interface. Models are configured in `lib/ai/models.ts` with per-model provider routing. Included models: Mistral, Moonshot, DeepSeek, OpenAI, and xAI.

### AI Gateway Authentication

**For Vercel deployments**: Authentication is handled automatically via OIDC tokens.

**For non-Vercel deployments**: You need to provide an AI Gateway API key by setting the `AI_GATEWAY_API_KEY` environment variable in your `.env.local` file.

With the [AI SDK](https://ai-sdk.dev/docs/introduction), you can also switch to direct LLM providers like [OpenAI](https://openai.com), [Anthropic](https://anthropic.com), [Cohere](https://cohere.com/), and [many more](https://ai-sdk.dev/providers/ai-sdk-providers) with just a few lines of code.

## Deploy Your Own

You can deploy your own version of Chatbot with billing to Vercel with one click:

[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fnarevai%2Fchatbot-with-billing&env=NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,CLERK_SECRET_KEY,AI_GATEWAY_API_KEY,POSTGRES_URL,POLAR_ACCESS_TOKEN,POLAR_SERVER&envDefaults=%7B%22POLAR_SERVER%22%3A%22sandbox%22%7D)

## Running locally

You will need to use the environment variables [defined in `.env.example`](.env.example) to run Chatbot. It's recommended you use [Vercel Environment Variables](https://vercel.com/docs/projects/environment-variables) for this, but a `.env` file is all that is necessary.

> Note: You should not commit your `.env` file or it will expose secrets that will allow others to control access to your various AI and authentication provider accounts.

```bash
pnpm install
pnpm db:migrate # Setup database or apply latest database changes
pnpm dev
```

Your app template should now be running on [localhost:3000](http://localhost:3000).
50 changes: 50 additions & 0 deletions examples/chatbot-with-billing-with-clerk/app/(chat)/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use server';

import { generateText, type UIMessage } from 'ai';
import { createOpenAI } from '@ai-sdk/openai';
import { auth } from '@clerk/nextjs/server';
import type { VisibilityType } from '@/components/chat/visibility-selector';
import { titlePrompt } from '@/lib/ai/prompts';
import {
getChatById,
getUserId,
updateChatVisibilityById,
} from '@/lib/db/queries';
import { getTextFromMessage } from '@/lib/utils';

export async function generateTitleFromUserMessage({
message,
}: {
message: UIMessage;
}) {
const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
const { text } = await generateText({
model: openai('gpt-4o-mini'),
system: titlePrompt,
prompt: getTextFromMessage(message),
});
return text
.replace(/^[#*"\s]+/, '')
.replace(/["]+$/, '')
.trim();
}

export async function updateChatVisibility({
chatId,
visibility,
}: {
chatId: string;
visibility: VisibilityType;
}) {
const dbUserId = await getUserId();
if (!dbUserId) {
throw new Error('Unauthorized');
}

const chat = await getChatById({ id: chatId });
if (!chat || chat.userId !== dbUserId) {
throw new Error('Unauthorized');
}

await updateChatVisibilityById({ chatId, visibility });
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { auth } from '@clerk/nextjs/server';
import { deleteChatById, getChatById, getUserId } from '@/lib/db/queries';
import { ChatbotError } from '@/lib/errors';

export async function DELETE(request: Request) {
const { searchParams } = new URL(request.url);
const id = searchParams.get('id');

if (!id) {
return new ChatbotError('bad_request:api').toResponse();
}

const { userId } = await auth();

if (!userId) {
return new ChatbotError('unauthorized:chat').toResponse();
}

const dbUserId = await getUserId();
if (!dbUserId) {
return new ChatbotError('unauthorized:chat').toResponse();
}

const chat = await getChatById({ id });

if (chat?.userId !== dbUserId) {
return new ChatbotError('forbidden:chat').toResponse();
}

const deletedChat = await deleteChatById({ id });

return Response.json(deletedChat, { status: 200 });
}
Loading
Loading