chatbot-template/components/chat/chat-header.tsx
dmitry.galkin 22a172e92d 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
2026-05-25 17:27:08 +04:00

56 lines
1.4 KiB
TypeScript

"use client";
import { PanelLeftIcon } from "lucide-react";
import { memo } from "react";
import { Button } from "@/components/ui/button";
import { useSidebar } from "@/components/ui/sidebar";
import { UsageBadge } from "./usage-badge";
import { VisibilitySelector, type VisibilityType } from "./visibility-selector";
function PureChatHeader({
chatId,
selectedVisibilityType,
isReadonly,
}: {
chatId: string;
selectedVisibilityType: VisibilityType;
isReadonly: boolean;
}) {
const { state, toggleSidebar, isMobile } = useSidebar();
if (state === "collapsed" && !isMobile) {
return null;
}
return (
<header className="sticky top-0 flex h-14 items-center gap-2 bg-sidebar px-3">
<Button
className="md:hidden"
onClick={toggleSidebar}
size="icon-sm"
variant="ghost"
>
<PanelLeftIcon className="size-4" />
</Button>
{!isReadonly && (
<VisibilitySelector
chatId={chatId}
selectedVisibilityType={selectedVisibilityType}
/>
)}
<div className="ml-auto flex items-center gap-2">
<UsageBadge />
</div>
</header>
);
}
export const ChatHeader = memo(PureChatHeader, (prevProps, nextProps) => {
return (
prevProps.chatId === nextProps.chatId &&
prevProps.selectedVisibilityType === nextProps.selectedVisibilityType &&
prevProps.isReadonly === nextProps.isReadonly
);
});