From 821522327c6733abc265ab43784c8268eccb3fb4 Mon Sep 17 00:00:00 2001 From: SkateSpots Date: Sun, 27 Sep 2026 15:36:09 +0700 Subject: [PATCH] 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. --- AGENTS.md | 2 ++ src/components/SasquatchGuide.tsx | 18 ++++++++++++++++++ src/hooks/useSasquatchAiStatus.ts | 4 ++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 7821501..3942bfb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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, diff --git a/src/components/SasquatchGuide.tsx b/src/components/SasquatchGuide.tsx index dd8f1fc..e0bff17 100644 --- a/src/components/SasquatchGuide.tsx +++ b/src/components/SasquatchGuide.tsx @@ -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([]); const [userInput, setUserInput] = useState(''); const [showChat, setShowChat] = useState(false); @@ -1547,6 +1554,17 @@ Instructions: ) : null} + {isOwner && aiStatus.data?.configured && aiStatus.data.balanceSats != null && ( + + ⚡ {aiStatus.data.balanceSats.toLocaleString()} sats + + )}
{activeMessage.title} diff --git a/src/hooks/useSasquatchAiStatus.ts b/src/hooks/useSasquatchAiStatus.ts index 9a9cf9e..1271665 100644 --- a/src/hooks/useSasquatchAiStatus.ts +++ b/src/hooks/useSasquatchAiStatus.ts @@ -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({ queryKey: ['sasquatch-ai-status', user?.pubkey], - enabled: isOwner, + enabled: (options.enabled ?? true) && isOwner, refetchInterval: 5 * 60 * 1000, staleTime: 60 * 1000, queryFn: async () => {