- 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.
63 lines
2.5 KiB
TypeScript
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>
|
|
);
|
|
}
|