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:
parent
69e1ae17b8
commit
821522327c
3 changed files with 22 additions and 2 deletions
|
|
@ -142,6 +142,8 @@ session with **no other memory of this project**. So:
|
||||||
Owner Settings shows the upstream balance (`SasquatchAiStatusCard`) via the
|
Owner Settings shows the upstream balance (`SasquatchAiStatusCard`) via the
|
||||||
owner-only `POST /api/ai/status` (NIP-98 + owner pubkey → `handleStatus`), with
|
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).
|
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
|
**AI answers are Pro-only**: free users get the static FAQ/glossary + local
|
||||||
relay lookups only (`if (isPro)` gates the AI block in `SasquatchGuide`).
|
relay lookups only (`if (isPro)` gates the AI block in `SasquatchGuide`).
|
||||||
Pro access (`useSasquatchPro`) is granted by a provider-authored zap receipt,
|
Pro access (`useSasquatchPro`) is granted by a provider-authored zap receipt,
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import { useCurrentUser } from '@/hooks/useCurrentUser';
|
||||||
import { useCVMClient } from '@/hooks/useCVMClient';
|
import { useCVMClient } from '@/hooks/useCVMClient';
|
||||||
import { usePersonalContext } from '@/hooks/usePersonalContext';
|
import { usePersonalContext } from '@/hooks/usePersonalContext';
|
||||||
import { useSasquatchPro } from '@/hooks/useSasquatchPro';
|
import { useSasquatchPro } from '@/hooks/useSasquatchPro';
|
||||||
|
import { useSasquatchAiStatus } from '@/hooks/useSasquatchAiStatus';
|
||||||
|
import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions';
|
||||||
import { SasquatchProDialog } from '@/components/SasquatchProDialog';
|
import { SasquatchProDialog } from '@/components/SasquatchProDialog';
|
||||||
import { useSasquatchActions, type SasquatchDraft } from '@/hooks/useSasquatchActions';
|
import { useSasquatchActions, type SasquatchDraft } from '@/hooks/useSasquatchActions';
|
||||||
import { SasquatchActionCard } from '@/components/SasquatchActionCard';
|
import { SasquatchActionCard } from '@/components/SasquatchActionCard';
|
||||||
|
|
@ -636,11 +638,16 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
||||||
const cvm = useCVMClient();
|
const cvm = useCVMClient();
|
||||||
const personal = usePersonalContext();
|
const personal = usePersonalContext();
|
||||||
const { isPro, receiptEvent } = useSasquatchPro();
|
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 { publishDraft } = useSasquatchActions();
|
||||||
const [proDialogOpen, setProDialogOpen] = useState(false);
|
const [proDialogOpen, setProDialogOpen] = useState(false);
|
||||||
|
|
||||||
const [isMinimized, setIsMinimized] = useLocalStorage('sasquatch-minimized', false);
|
const [isMinimized, setIsMinimized] = useLocalStorage('sasquatch-minimized', false);
|
||||||
const [isVisible, setIsVisible] = useState(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 [chatMessages, setChatMessages] = useState<ChatMessage[]>([]);
|
||||||
const [userInput, setUserInput] = useState('');
|
const [userInput, setUserInput] = useState('');
|
||||||
const [showChat, setShowChat] = useState(false);
|
const [showChat, setShowChat] = useState(false);
|
||||||
|
|
@ -1547,6 +1554,17 @@ Instructions:
|
||||||
</button>
|
</button>
|
||||||
) : null}
|
) : null}
|
||||||
<CVMStatus connected={cvmConnected} />
|
<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>
|
||||||
<div className="text-[10px] text-[#ccff00]/50 leading-none mt-0.5">
|
<div className="text-[10px] text-[#ccff00]/50 leading-none mt-0.5">
|
||||||
{activeMessage.title}
|
{activeMessage.title}
|
||||||
|
|
|
||||||
|
|
@ -21,13 +21,13 @@ const AI_STATUS_URL = Capacitor.isNativePlatform()
|
||||||
* nudged to top up before AI silently degrades to the keyless fallback. Polled
|
* nudged to top up before AI silently degrades to the keyless fallback. Polled
|
||||||
* every few minutes. Returns `undefined`/skips for non-owners.
|
* every few minutes. Returns `undefined`/skips for non-owners.
|
||||||
*/
|
*/
|
||||||
export function useSasquatchAiStatus() {
|
export function useSasquatchAiStatus(options: { enabled?: boolean } = {}) {
|
||||||
const { user } = useCurrentUser();
|
const { user } = useCurrentUser();
|
||||||
const isOwner = !!user?.pubkey && PLATFORM_OWNER_PUBKEYS.includes(user.pubkey);
|
const isOwner = !!user?.pubkey && PLATFORM_OWNER_PUBKEYS.includes(user.pubkey);
|
||||||
|
|
||||||
return useQuery<SasquatchAiStatus>({
|
return useQuery<SasquatchAiStatus>({
|
||||||
queryKey: ['sasquatch-ai-status', user?.pubkey],
|
queryKey: ['sasquatch-ai-status', user?.pubkey],
|
||||||
enabled: isOwner,
|
enabled: (options.enabled ?? true) && isOwner,
|
||||||
refetchInterval: 5 * 60 * 1000,
|
refetchInterval: 5 * 60 * 1000,
|
||||||
staleTime: 60 * 1000,
|
staleTime: 60 * 1000,
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue