mirror of
https://github.com/browseros-ai/BrowserOS.git
synced 2026-05-13 15:46:22 +00:00
* feat: add credit-based tracking for BrowserOS provider Send X-BrowserOS-ID header on all LLM requests through the BrowserOS gateway for per-installation credit tracking. Handle 429 CREDITS_EXHAUSTED as non-retryable. Add GET/PUT /credits endpoints to check and manage credit balance. * docs: add credits tracking UI design Design for showing credit balance in side panel chat header (color-coded badge) and a dedicated Usage & Billing settings page. Credits refresh after each completed message turn or on exhaustion error. * docs: add credits tracking UI implementation plan 8-task plan covering useCredits hook, CreditBadge component, ChatHeader integration, message completion refresh, ChatError CREDITS_EXHAUSTED handling, Usage & Billing settings page, and route/sidebar registration. * feat: add useCredits React Query hook * feat: add CreditBadge component with color thresholds * feat: show credit badge in chat header for BrowserOS provider * feat: refresh credits after chat message completion and on error * feat: handle CREDITS_EXHAUSTED error in chat * feat: add Usage & Billing settings page * feat: register usage page route and sidebar entry * fix: lint and formatting fixes for credit tracking UI * fix: separate credits exhausted from Kimi rate limit in ChatError, redesign Usage page * chore: remove PUT /credits endpoint and setCredits function * fix: extract shared credit colors, add error state to UsagePage, use dailyLimit from gateway * fix: make dailyLimit required in CreditsInfo (gateway always returns it) * feat: gate credits UI behind CREDITS_SUPPORT feature flag (server >= 0.0.78)
126 lines
4.3 KiB
TypeScript
126 lines
4.3 KiB
TypeScript
import { AlertCircle, Clock, Coins, CreditCard, Zap } from 'lucide-react'
|
|
import type { FC } from 'react'
|
|
import { Button } from '@/components/ui/button'
|
|
import {
|
|
getCreditBarColor,
|
|
getCreditTextColor,
|
|
} from '@/lib/credits/credit-colors'
|
|
import { useCredits } from '@/lib/credits/useCredits'
|
|
import { BrowserOSIcon } from '@/lib/llm-providers/providerIcons'
|
|
import { cn } from '@/lib/utils'
|
|
|
|
export const UsagePage: FC = () => {
|
|
const { data, isLoading, error } = useCredits()
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<div className="flex items-center justify-center p-12 text-muted-foreground text-sm">
|
|
Loading usage data...
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div className="space-y-6 p-6">
|
|
<div className="flex items-center gap-4 rounded-xl border p-5">
|
|
<BrowserOSIcon size={40} />
|
|
<div>
|
|
<h2 className="font-semibold text-lg">Usage & Billing</h2>
|
|
<p className="text-muted-foreground text-sm">
|
|
Monitor your BrowserOS AI credit usage
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex flex-col items-center gap-3 rounded-xl border border-destructive/30 bg-destructive/5 p-8">
|
|
<AlertCircle className="h-6 w-6 text-muted-foreground" />
|
|
<p className="text-muted-foreground text-sm">
|
|
Unable to load credit information
|
|
</p>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const credits = data?.credits ?? 0
|
|
const total = data?.dailyLimit ?? 100
|
|
const percentage = Math.min((credits / total) * 100, 100)
|
|
|
|
return (
|
|
<div className="space-y-6 p-6">
|
|
<div className="flex items-center gap-4 rounded-xl border p-5">
|
|
<BrowserOSIcon size={40} />
|
|
<div>
|
|
<h2 className="font-semibold text-lg">Usage & Billing</h2>
|
|
<p className="text-muted-foreground text-sm">
|
|
Monitor your BrowserOS AI credit usage
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-xl border p-5">
|
|
<div className="mb-4 flex items-center justify-between">
|
|
<div className="flex items-center gap-2">
|
|
<Coins className="h-5 w-5 text-muted-foreground" />
|
|
<span className="font-semibold text-sm">Daily Credits</span>
|
|
</div>
|
|
<span
|
|
className={cn('font-bold text-2xl', getCreditTextColor(credits))}
|
|
>
|
|
{credits}
|
|
<span className="ml-1 font-normal text-muted-foreground text-sm">
|
|
/ {total}
|
|
</span>
|
|
</span>
|
|
</div>
|
|
|
|
<div className="mb-5 h-2.5 w-full overflow-hidden rounded-full bg-muted">
|
|
<div
|
|
className={cn(
|
|
'h-full rounded-full transition-all duration-500',
|
|
getCreditBarColor(credits),
|
|
)}
|
|
style={{ width: `${percentage}%` }}
|
|
/>
|
|
</div>
|
|
|
|
<div className="grid grid-cols-2 gap-3">
|
|
<div className="flex items-center gap-2.5 rounded-lg bg-muted/50 px-3 py-2.5">
|
|
<Clock className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
<div>
|
|
<p className="font-medium text-xs">Resets daily</p>
|
|
<p className="text-muted-foreground text-xs">Midnight UTC</p>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center gap-2.5 rounded-lg bg-muted/50 px-3 py-2.5">
|
|
<Zap className="h-4 w-4 shrink-0 text-muted-foreground" />
|
|
<div>
|
|
<p className="font-medium text-xs">Credits used today</p>
|
|
<p className="text-muted-foreground text-xs">
|
|
{total - credits} of {total}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="rounded-xl border p-5">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex items-center gap-3">
|
|
<CreditCard className="h-5 w-5 text-muted-foreground" />
|
|
<div>
|
|
<p className="font-semibold text-sm">Need more credits?</p>
|
|
<p className="text-muted-foreground text-xs">
|
|
Additional credit packages coming soon
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<Button variant="outline" size="sm" disabled className="opacity-50">
|
|
Add Credits
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|