SkateSpots/src/components/SasquatchAiStatusCard.tsx
SkateSpots 62b395f3d2 feat(sasquatch): align client models with the node + owner AI-balance nudge
- sasquatchBrain: PROXY_MODELS now match the Routstr node's slugs
  (gemini-3-6-flash, z-ai-glm-5-3, gemini-3-7-flash).
- ai.mjs: new owner-only POST /api/ai/status (NIP-98 + owner pubkey) returning the
  upstream balance + a 'low' flag (<500 sats); config.path now serves both routes.
- useSasquatchAiStatus + SasquatchAiStatusCard (owner-only, in Settings): shows the
  balance and warns when low — the top-up nudge, no webhook/notification key needed.
2026-09-27 15:27:32 +07:00

63 lines
2.5 KiB
TypeScript

import { AlertTriangle, Coins, Loader2 } from 'lucide-react';
import { Card, CardContent } from '@/components/ui/card';
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions';
import { useSasquatchAiStatus } from '@/hooks/useSasquatchAiStatus';
import { cn } from '@/lib/utils';
/**
* Owner-only card: the Sasquatch AI (Routstr node) balance, with a warning when
* it's running low. This is the "top up before AI degrades" nudge — no webhook
* needed, since the operator can see it live in Settings.
*/
export function SasquatchAiStatusCard() {
const { user } = useCurrentUser();
const isOwner = !!user?.pubkey && PLATFORM_OWNER_PUBKEYS.includes(user.pubkey);
const { data, isLoading, isError } = useSasquatchAiStatus();
if (!isOwner) return null;
const low = data?.low === true;
const balance = data?.balanceSats;
return (
<Card className={cn('overflow-hidden bg-white/2', low ? 'border-amber-500/40' : 'border-white/10')}>
<CardContent className="py-3.5">
<div className="flex items-center justify-between gap-3">
<p className="flex items-center gap-2 text-sm font-bold text-white">
{low ? (
<AlertTriangle className="h-4 w-4 text-amber-400" />
) : (
<Coins className="h-4 w-4 text-red-400" />
)}
Sasquatch AI
<span className="rounded bg-red-500/10 px-1.5 py-0.5 text-[9px] font-black uppercase tracking-widest text-red-400/70">
Owner
</span>
</p>
<span
className={cn(
'shrink-0 text-xs font-bold',
isError ? 'text-gray-500' : low ? 'text-amber-400' : 'text-green-400',
)}
>
{isLoading ? (
<Loader2 className="h-4 w-4 animate-spin text-muted-foreground" />
) : isError || data?.configured === false ? (
'not configured'
) : balance === null || balance === undefined ? (
'unavailable'
) : (
`${balance.toLocaleString()} sats`
)}
</span>
</div>
<p className="mt-1 text-xs text-muted-foreground">
{low
? `Running low — top up at ${data?.provider ?? 'the node'} or AI will fall back to the keyless provider.`
: 'Pay-per-request AI, funded in sats via Routstr. Tops up when low.'}
</p>
</CardContent>
</Card>
);
}