Same proxy as netlify/functions/ai.mjs, rebuilt as a standalone Bun service on
the CVM host so AI calls stop spending Netlify compute/bandwidth credits and
the sats-paid Routstr key lives on our box.
- ai-proxy/server.ts: Bun.serve on 127.0.0.1:8787 — NIP-98 + Pro gate, model
allowlist, per-IP throttle, /api/ai + /api/ai/status. Own tsconfig
(typecheck:ai in npm test); eslint-ignored like cvm-server.
- ai-proxy/{package.json,.env.example,skatespots-ai.service,Caddyfile,DEPLOY.md}.
- scripts/deploy-ai-proxy.sh (npm run deploy:ai): scp + bun install + unit +
restart, mirroring deploy-cvm.sh.
- Client: one place decides the AI base, src/lib/sasquatchAi.ts (also the NIP-98
u tag). Defaults unchanged (/api/ai on Netlify); set VITE_SASQUATCH_AI_BASE to
move it. Fully reversible.
Deployed and verified on the host: /api/ai/status returns the live Routstr
balance. Public TLS (DNS + Caddy) is the remaining step before switching the app
over, so no client behaviour changes yet.
43 lines
1.6 KiB
TypeScript
43 lines
1.6 KiB
TypeScript
import { useQuery } from '@tanstack/react-query';
|
|
import { useCurrentUser } from '@/hooks/useCurrentUser';
|
|
import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions';
|
|
import { sasquatchAiStatusUrl } from '@/lib/sasquatchAi';
|
|
|
|
/** Report shape from the proxy's owner-only `/api/ai/status`. */
|
|
export interface SasquatchAiStatus {
|
|
configured: boolean;
|
|
balanceSats: number | null;
|
|
low: boolean;
|
|
provider?: string;
|
|
}
|
|
|
|
const AI_STATUS_URL = sasquatchAiStatusUrl();
|
|
|
|
/**
|
|
* Owner-only: the Sasquatch AI (upstream) account balance, so the operator can be
|
|
* 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(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: (options.enabled ?? true) && isOwner,
|
|
refetchInterval: 5 * 60 * 1000,
|
|
staleTime: 60 * 1000,
|
|
queryFn: async () => {
|
|
// No signature required — see handleStatus(); avoids an Amber prompt just
|
|
// to show a balance.
|
|
const res = await fetch(AI_STATUS_URL, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: '{}',
|
|
signal: AbortSignal.timeout(10000),
|
|
});
|
|
if (!res.ok) throw new Error(`AI status ${res.status}`);
|
|
return (await res.json()) as SasquatchAiStatus;
|
|
},
|
|
});
|
|
}
|