feat(sasquatch): show the owner AI balance on the panel header

Owner-only chip (next to the Pro/CVM chips) showing the upstream sats balance,
amber when low. useSasquatchAiStatus gains an 'enabled' option; the panel only
fetches while it is open, so the NIP-98 signature never fires at app startup.
This commit is contained in:
SkateSpots 2026-09-27 15:36:09 +07:00
parent 69e1ae17b8
commit 821522327c
3 changed files with 22 additions and 2 deletions

View file

@ -142,6 +142,8 @@ session with **no other memory of this project**. So:
Owner Settings shows the upstream balance (`SasquatchAiStatusCard`) via the
owner-only `POST /api/ai/status` (NIP-98 + owner pubkey → `handleStatus`), with
a "low balance" warning below 500 sats — the top-up nudge (no webhook needed).
The same balance shows as a chip in the Sasquatch panel header (owner-only,
fetched only while the panel is open so it never prompts at startup).
**AI answers are Pro-only**: free users get the static FAQ/glossary + local
relay lookups only (`if (isPro)` gates the AI block in `SasquatchGuide`).
Pro access (`useSasquatchPro`) is granted by a provider-authored zap receipt,

View file

@ -17,6 +17,8 @@ import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useCVMClient } from '@/hooks/useCVMClient';
import { usePersonalContext } from '@/hooks/usePersonalContext';
import { useSasquatchPro } from '@/hooks/useSasquatchPro';
import { useSasquatchAiStatus } from '@/hooks/useSasquatchAiStatus';
import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions';
import { SasquatchProDialog } from '@/components/SasquatchProDialog';
import { useSasquatchActions, type SasquatchDraft } from '@/hooks/useSasquatchActions';
import { SasquatchActionCard } from '@/components/SasquatchActionCard';
@ -636,11 +638,16 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
const cvm = useCVMClient();
const personal = usePersonalContext();
const { isPro, receiptEvent } = useSasquatchPro();
// Owner-only: show the AI (Routstr) balance on the panel — fetched only while
// the panel is open, so it never prompts at startup.
const isOwner = !!user?.pubkey && PLATFORM_OWNER_PUBKEYS.includes(user.pubkey);
const { publishDraft } = useSasquatchActions();
const [proDialogOpen, setProDialogOpen] = useState(false);
const [isMinimized, setIsMinimized] = useLocalStorage('sasquatch-minimized', false);
const [isVisible, setIsVisible] = useState(false);
// Fetched only while the panel is open (owner-only).
const aiStatus = useSasquatchAiStatus({ enabled: isOwner && isVisible && !isMinimized });
const [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
const [userInput, setUserInput] = useState('');
const [showChat, setShowChat] = useState(false);
@ -1547,6 +1554,17 @@ Instructions:
</button>
) : null}
<CVMStatus connected={cvmConnected} />
{isOwner && aiStatus.data?.configured && aiStatus.data.balanceSats != null && (
<span
className={cn(
'flex items-center gap-0.5 rounded px-1 py-0.5 text-[8px] font-black uppercase tracking-wider',
aiStatus.data.low ? 'bg-amber-500/20 text-amber-400' : 'bg-emerald-500/15 text-emerald-400',
)}
title={`Sasquatch AI balance — top up at ${aiStatus.data.provider ?? 'the node'}`}
>
⚡ {aiStatus.data.balanceSats.toLocaleString()} sats
</span>
)}
</div>
<div className="text-[10px] text-[#ccff00]/50 leading-none mt-0.5">
{activeMessage.title}

View file

@ -21,13 +21,13 @@ const AI_STATUS_URL = Capacitor.isNativePlatform()
* nudged to top up before AI silently degrades to the keyless fallback. Polled
* every few minutes. Returns `undefined`/skips for non-owners.
*/
export function useSasquatchAiStatus() {
export function useSasquatchAiStatus(options: { enabled?: boolean } = {}) {
const { user } = useCurrentUser();
const isOwner = !!user?.pubkey && PLATFORM_OWNER_PUBKEYS.includes(user.pubkey);
return useQuery<SasquatchAiStatus>({
queryKey: ['sasquatch-ai-status', user?.pubkey],
enabled: isOwner,
enabled: (options.enabled ?? true) && isOwner,
refetchInterval: 5 * 60 * 1000,
staleTime: 60 * 1000,
queryFn: async () => {