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:
parent
3e21c2334c
commit
22a172e92d
23 changed files with 1374 additions and 86 deletions
|
|
@ -1,14 +0,0 @@
|
|||
import type { UserType } from "@/app/(auth)/auth";
|
||||
|
||||
type Entitlements = {
|
||||
maxMessagesPerHour: number;
|
||||
};
|
||||
|
||||
export const entitlementsByUserType: Record<UserType, Entitlements> = {
|
||||
guest: {
|
||||
maxMessagesPerHour: 10,
|
||||
},
|
||||
regular: {
|
||||
maxMessagesPerHour: 10,
|
||||
},
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
export const DEFAULT_CHAT_MODEL = "claude-sonnet";
|
||||
export const DEFAULT_CHAT_MODEL = "gpt-5.4-mini";
|
||||
|
||||
export type ModelCapabilities = {
|
||||
tools: boolean;
|
||||
|
|
|
|||
40
lib/ai/tools/with-limit.ts
Normal file
40
lib/ai/tools/with-limit.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import type { Tool } from "ai";
|
||||
import type { LimitKey } from "@/lib/subscription/config";
|
||||
import {
|
||||
checkAndConsume,
|
||||
LimitExceededError,
|
||||
} from "@/lib/subscription/service";
|
||||
|
||||
export function withLimit<T extends Tool>(
|
||||
tool: T,
|
||||
kind: LimitKey,
|
||||
userId: string
|
||||
): T {
|
||||
const originalExecute = tool.execute;
|
||||
if (!originalExecute) {
|
||||
return tool;
|
||||
}
|
||||
|
||||
// biome-ignore lint/suspicious/noExplicitAny: wrapper has to be generic across all tool signatures
|
||||
const wrappedExecute = (async (input: any, options: any) => {
|
||||
try {
|
||||
await checkAndConsume(userId, kind);
|
||||
} catch (error) {
|
||||
if (error instanceof LimitExceededError) {
|
||||
return {
|
||||
error: "limit_exceeded",
|
||||
kind: error.kind,
|
||||
tier: error.tier,
|
||||
used: error.used,
|
||||
limit: error.limit,
|
||||
message: `You've hit your ${kind.replace("_", " ")} limit for today (${error.used}/${error.limit} on ${error.tier} tier). Upgrade to Pro for higher limits.`,
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
return originalExecute(input, options);
|
||||
// biome-ignore lint/suspicious/noExplicitAny: tool execute signature varies
|
||||
}) as any;
|
||||
|
||||
return { ...tool, execute: wrappedExecute } as T;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue