SkateSpots/src/components/CVMHealthCheck.tsx
shakespeare.diy 5506344893 Make Sasquatch tell the truth: direct relay queries, not a CVM server
The chat's data layer (useCVMClient) queries Nostr relays directly from the
browser — the CVM/ContextVM server round-trip was removed earlier for speed.
But the UI and system prompt still claimed data came "from the CVM server",
which is no longer true. Aligned every user-facing claim with reality:

- System prompt: reframed "CVM Tools / MUST route through CVM" as "Live data
  tools that query the Nostr relays directly". Tool-calling behaviour unchanged.
- Streaming status: "Querying CVM for X" → "Searching the relays for X".
- Tool-failure message: no longer blames a non-existent "CVM server"; now
  correctly frames it as relays being slow, with a retry nudge.
- Empty results: stop asserting "not added yet" as fact — an empty result can
  also mean slow relays or a misspelled place. Now offers retry + add-a-spot,
  and names the place searched. Applied to the local formatter, the system
  prompt rules, and the second-pass AI instruction.
- Chat footer: "Powered by ContextVM & Nostr Relays Data" → "Live data from
  Nostr relays" (links the primary community relay).
- About page credit: "AI powered by ContextVM" → "Trust & crew chat via
  ContextVM" (its real role: Relatr trust scores + encrypted crew chat).
- Owner CVM health panel: clarified it probes the standalone MCP server and
  that the in-app chat does not depend on it.
- Updated stale internal comments in SasquatchGuide + CreateSpotPage.

ContextVM references that ARE accurate (Relatr trust scores, crew-chat Cordn
coordinator) were left intact. Build clean, no console errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-10 00:16:59 +07:00

193 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* CVMHealthCheck — owner-only status probe for the SkateSpots ContextVM server.
*
* Renders NOTHING unless the logged-in user is one of the two platform owners
* (PLATFORM_OWNER_PUBKEYS). Pings the ContextVM relay for the server's own
* events and reports online / stale / offline with a last-seen timestamp.
*/
import { useCurrentUser } from '@/hooks/useCurrentUser';
import { PLATFORM_OWNER_PUBKEYS } from '@/hooks/useListingRestrictions';
import {
useCVMServerHealth,
CVM_SERVER_PUBKEY,
CVM_RELAY,
type CVMHealthStatus,
} from '@/hooks/useCVMServerHealth';
import { cn } from '@/lib/utils';
import {
Activity,
CheckCircle2,
XCircle,
AlertTriangle,
Loader2,
ExternalLink,
} from 'lucide-react';
function timeAgo(unixSeconds: number): string {
const s = Math.floor(Date.now() / 1000 - unixSeconds);
if (s < 60) return `${s}s ago`;
const m = Math.floor(s / 60);
if (m < 60) return `${m}m ago`;
const h = Math.floor(m / 60);
if (h < 24) return `${h}h ago`;
const d = Math.floor(h / 24);
return `${d}d ago`;
}
const STATUS_META: Record<
CVMHealthStatus,
{ label: string; color: string; ring: string; dot: string }
> = {
idle: { label: 'Not checked', color: 'text-gray-400', ring: 'border-white/10', dot: 'bg-gray-500' },
checking: { label: 'Checking…', color: 'text-blue-300', ring: 'border-blue-500/30', dot: 'bg-blue-400' },
online: { label: 'Online', color: 'text-green-400', ring: 'border-green-500/30', dot: 'bg-green-400' },
stale: { label: 'Stale', color: 'text-amber-400', ring: 'border-amber-500/30', dot: 'bg-amber-400' },
offline: { label: 'Offline', color: 'text-red-400', ring: 'border-red-500/30', dot: 'bg-red-500' },
error: { label: 'Unreachable', color: 'text-red-400', ring: 'border-red-500/30', dot: 'bg-red-500' },
};
export function CVMHealthCheck() {
const { user } = useCurrentUser();
const health = useCVMServerHealth();
// Owner-only gate. Never render for anyone else.
const isOwner = !!user?.pubkey && PLATFORM_OWNER_PUBKEYS.includes(user.pubkey);
if (!isOwner) return null;
const meta = STATUS_META[health.status];
const StatusIcon =
health.status === 'checking'
? Loader2
: health.status === 'online'
? CheckCircle2
: health.status === 'stale'
? AlertTriangle
: health.status === 'idle'
? Activity
: XCircle;
return (
<div className={cn('rounded-2xl border overflow-hidden bg-white/2', meta.ring)}>
<div className="px-4 py-3.5 border-b border-white/5 flex items-center justify-between gap-3">
<div className="min-w-0">
<p className="text-sm font-bold text-white flex items-center gap-2">
<Activity className="h-4 w-4 text-red-400" />
ContextVM Server
<span className="text-[9px] font-black uppercase tracking-widest text-red-400/70 bg-red-500/10 px-1.5 py-0.5 rounded">
Owner
</span>
</p>
<p className="text-xs text-muted-foreground mt-0.5">
Heartbeat probe for the standalone ContextVM MCP server. Note: the
in-app Sasquatch chat reads Nostr relays directly and does not depend
on this server.
</p>
</div>
<span
className={cn(
'shrink-0 flex items-center gap-1.5 text-xs font-bold px-2.5 py-1 rounded-full border',
meta.ring,
meta.color,
)}
>
<span className={cn('h-1.5 w-1.5 rounded-full', meta.dot)} />
{meta.label}
</span>
</div>
<div className="px-4 py-3.5 space-y-3">
{/* Status detail line */}
<div className="flex items-start gap-2.5">
<StatusIcon
className={cn(
'h-4 w-4 mt-0.5 shrink-0',
meta.color,
health.status === 'checking' && 'animate-spin',
)}
/>
<div className="text-xs leading-relaxed">
{health.status === 'idle' && (
<span className="text-muted-foreground">
Tap “Check server” to ping the ContextVM relay for the server’s heartbeat.
</span>
)}
{health.status === 'checking' && (
<span className="text-blue-300">Querying {CVM_RELAY}…</span>
)}
{health.status === 'online' && health.lastSeen && (
<span className="text-green-400">
Server is announcing. Last event {timeAgo(health.lastSeen)}.
</span>
)}
{health.status === 'stale' && health.lastSeen && (
<span className="text-amber-400">
Server found, but last activity was {timeAgo(health.lastSeen)} — it may be
down or idle.
</span>
)}
{health.status === 'offline' && (
<span className="text-red-400">
No events from the server on the relay. It’s likely not running.
</span>
)}
{health.status === 'error' && (
<span className="text-red-400">{health.error}</span>
)}
</div>
</div>
{/* Meta grid */}
{(health.eventCount > 0 || health.checkedAt) && (
<div className="grid grid-cols-2 gap-2 text-[11px]">
<div className="rounded-lg bg-black/30 border border-white/5 px-2.5 py-2">
<p className="text-gray-500 uppercase tracking-wide font-bold text-[9px]">
Events found
</p>
<p className="text-white font-mono">{health.eventCount}</p>
</div>
<div className="rounded-lg bg-black/30 border border-white/5 px-2.5 py-2">
<p className="text-gray-500 uppercase tracking-wide font-bold text-[9px]">
Last seen
</p>
<p className="text-white font-mono">
{health.lastSeen ? timeAgo(health.lastSeen) : '—'}
</p>
</div>
</div>
)}
{/* Note — Sasquatch does NOT depend on this server. */}
<p className="text-[11px] text-muted-foreground/70 leading-relaxed">
Note: Sasquatch queries data relays directly — it keeps working even if this
server is down. This check is for operations only.
</p>
{/* Actions */}
<div className="flex items-center gap-2 pt-0.5">
<button
onClick={health.check}
disabled={health.status === 'checking'}
className="flex items-center gap-2 text-sm font-bold text-red-400 hover:text-red-300 disabled:opacity-50 transition-colors"
>
{health.status === 'checking' ? (
<Loader2 className="h-4 w-4 animate-spin" />
) : (
<Activity className="h-4 w-4" />
)}
Check server
</button>
<a
href={`https://contextvm.org/s/${CVM_SERVER_PUBKEY}`}
target="_blank"
rel="noopener noreferrer"
className="ml-auto flex items-center gap-1.5 text-xs font-semibold text-gray-400 hover:text-white transition-colors"
>
Directory <ExternalLink className="h-3.5 w-3.5" />
</a>
</div>
</div>
</div>
);
}