'use client'; import { useState } from 'react'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { Checkbox } from '@/components/ui/checkbox'; import { apiFetch } from '@/lib/api/client'; type Period = 'day' | 'week' | 'month'; interface BudgetResp { data: { budget: { enabled: boolean; softCapTokens: number; hardCapTokens: number; period: Period }; used: number; breakdown: Array<{ feature: string; tokens: number; calls: number }>; }; } function formatNum(n: number): string { return n.toLocaleString(); } export function AiBudgetCard() { const qc = useQueryClient(); const queryKey = ['admin-ai-budget']; const { data, isLoading } = useQuery({ queryKey, queryFn: () => apiFetch('/api/v1/admin/ai-budget'), }); // Key-based remount: the form body is keyed on the loaded payload // signature so its useState initializers seed from server data on // first load. Replaces the prior useEffect(setState, [data]) sync. const sig = data?.data ? `${data.data.budget.enabled}:${data.data.budget.softCapTokens}:${data.data.budget.hardCapTokens}:${data.data.budget.period}` : 'loading'; return ( ); } function AiBudgetCardBody({ data, isLoading, qc, queryKey, }: { data: BudgetResp | undefined; isLoading: boolean; qc: ReturnType; queryKey: string[]; }) { const [enabled, setEnabled] = useState(data?.data.budget.enabled ?? false); const [softCap, setSoftCap] = useState(data ? String(data.data.budget.softCapTokens) : '100000'); const [hardCap, setHardCap] = useState(data ? String(data.data.budget.hardCapTokens) : '500000'); const [period, setPeriod] = useState(data?.data.budget.period ?? 'month'); const save = useMutation({ mutationFn: () => apiFetch('/api/v1/admin/ai-budget', { method: 'PUT', body: { enabled, softCapTokens: Number.parseInt(softCap || '0', 10), hardCapTokens: Number.parseInt(hardCap || '0', 10), period, }, }), onSuccess: () => qc.invalidateQueries({ queryKey }), }); if (isLoading) { return ( AI cost guardrails Loading… ); } const used = data?.data.used ?? 0; const hard = data?.data.budget.hardCapTokens ?? 0; const soft = data?.data.budget.softCapTokens ?? 0; const pctOfHard = hard > 0 ? Math.min(100, Math.round((used / hard) * 100)) : 0; const breakdown = data?.data.breakdown ?? []; return ( AI cost guardrails

Cap how many AI tokens this port can spend per period. The hard cap blocks new calls; the soft cap surfaces a warning banner. Tokens are the unit both OpenAI and Anthropic bill on, so the cap survives model price changes.

This {period}: {formatNum(used)} tokens soft {formatNum(soft)} · hard {formatNum(hard)}
= hard ? 'bg-destructive' : used >= soft ? 'bg-amber-500' : 'bg-emerald-500' }`} style={{ width: `${pctOfHard}%` }} />
{breakdown.length > 0 ? (
    {breakdown.map((b) => (
  • {b.feature.replace(/_/g, ' ')} {formatNum(b.tokens)} tokens · {b.calls} call{b.calls === 1 ? '' : 's'}
  • ))}
) : null}
setEnabled(v === true)} />

When off, usage is still recorded for visibility but no requests are blocked.

setSoftCap(e.target.value)} disabled={!enabled} />
setHardCap(e.target.value)} disabled={!enabled} />
); }