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>
This commit is contained in:
parent
ee23f3e063
commit
5506344893
4 changed files with 37 additions and 29 deletions
|
|
@ -80,7 +80,9 @@ export function CVMHealthCheck() {
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-xs text-muted-foreground mt-0.5">
|
<p className="text-xs text-muted-foreground mt-0.5">
|
||||||
Health probe for the Sasquatch CVM data server
|
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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
* SasquatchGuide — AI-powered skate assistant
|
* SasquatchGuide — AI-powered skate assistant
|
||||||
* ─────────────────────────────────────────────────────────────────────────────
|
* ─────────────────────────────────────────────────────────────────────────────
|
||||||
* Uses OpenRouter free model (no credits needed, works for any user) for
|
* Uses OpenRouter free model (no credits needed, works for any user) for
|
||||||
* natural language understanding, then calls the SkateSpots CVM server on
|
* natural language understanding, then queries the Nostr relays directly
|
||||||
* Nostr for live data (spots, events, coaches, gear).
|
* (useCVMClient) for live data (spots, events, coaches, gear).
|
||||||
* Falls back to static FAQ if AI is unavailable.
|
* Falls back to static FAQ if AI is unavailable.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -126,15 +126,15 @@ interface ChatMessage {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ─── System prompt for the AI ─────────────────────────────────────────────────
|
// ─── System prompt for the AI ─────────────────────────────────────────────────
|
||||||
// Sasquatch is the user-facing agent. The CVM server is the data layer —
|
// Sasquatch is the user-facing agent. The data tools below query Nostr relays
|
||||||
// it aggregates and crunches Nostr events across multiple relays, deduplicates,
|
// directly from the browser (see useCVMClient) — they aggregate events across
|
||||||
// filters by features inside events (things Nostr filters alone can't do),
|
// the community relays, deduplicate, and run feature filters that plain Nostr
|
||||||
// and returns structured results. Sasquatch MUST always route data queries
|
// filters can't. Sasquatch MUST always answer data questions by calling a tool —
|
||||||
// through CVM — never guess from training data.
|
// never guess spots/events/people from training data.
|
||||||
|
|
||||||
const SYSTEM_PROMPT = `You are The Sasquatch, the AI guide for SkateSpots — a decentralized skateboarding platform built on Nostr. Chill, skater-friendly, concise.
|
const SYSTEM_PROMPT = `You are The Sasquatch, the AI guide for SkateSpots — a decentralized skateboarding platform built on Nostr. Chill, skater-friendly, concise.
|
||||||
|
|
||||||
## CVM Tools — query live data
|
## Live data tools — query the Nostr relays
|
||||||
Respond with ONLY a JSON tool call when you need data. Nothing else on that line.
|
Respond with ONLY a JSON tool call when you need data. Nothing else on that line.
|
||||||
{"tool":"<name>","args":{}}
|
{"tool":"<name>","args":{}}
|
||||||
|
|
||||||
|
|
@ -143,7 +143,7 @@ Args: city?(string), country?(string), difficulty?("beginner"|"intermediate"|"ad
|
||||||
- Filter checks: city tag, location text, spot title, description — all case-insensitive
|
- Filter checks: city tag, location text, spot title, description — all case-insensitive
|
||||||
- For country queries use city of the capital/main city too: Thailand → use location:"Bangkok"
|
- For country queries use city of the capital/main city too: Thailand → use location:"Bangkok"
|
||||||
- Always use limit:20+ for city/country searches
|
- Always use limit:20+ for city/country searches
|
||||||
- 0 results = not added yet, encourage user to add via skate-spots.org/map
|
- 0 results could mean none listed yet OR the relays were slow — be honest, suggest a retry and adding via skate-spots.org/map (don't state "nobody added one" as a fact)
|
||||||
Example: {"tool":"find_spots","args":{"city":"Bangkok","limit":20}}
|
Example: {"tool":"find_spots","args":{"city":"Bangkok","limit":20}}
|
||||||
|
|
||||||
### find_events
|
### find_events
|
||||||
|
|
@ -200,7 +200,7 @@ Note: this returns a rich itinerary CARD — after calling it, add a short one-l
|
||||||
3. App/protocol/how-to questions → answer directly (no tool). Wallet, Supporters, and the private Stash have NO live search tool — answer from the App facts below and point users to the right page. Spots, Events, the Lab (all crafts incl. judges), Gear, Crews, Shops and Tours all HAVE live tools — use them for "find/show" requests.
|
3. App/protocol/how-to questions → answer directly (no tool). Wallet, Supporters, and the private Stash have NO live search tool — answer from the App facts below and point users to the right page. Spots, Events, the Lab (all crafts incl. judges), Gear, Crews, Shops and Tours all HAVE live tools — use them for "find/show" requests.
|
||||||
4. Be concise, skater-friendly
|
4. Be concise, skater-friendly
|
||||||
5. Never invent spot names, event dates, or user data
|
5. Never invent spot names, event dates, or user data
|
||||||
6. 0 results = honest "none listed yet" + encourage adding content
|
6. 0 results → be honest: it may be none listed yet OR the relays were slow. Suggest a retry + adding content; never assert "nobody's added one" as fact
|
||||||
|
|
||||||
## App facts (features & how they connect)
|
## App facts (features & how they connect)
|
||||||
- Nostr = decentralized protocol; your cryptographic keys ARE your identity — one login works everywhere, nobody can ban you
|
- Nostr = decentralized protocol; your cryptographic keys ARE your identity — one login works everywhere, nobody can ban you
|
||||||
|
|
@ -411,7 +411,14 @@ function formatLocalResults(intent: LocalIntent, result: unknown): string {
|
||||||
intent.tool === 'find_tours' ? 'tours' : 'gear listings';
|
intent.tool === 'find_tours' ? 'tours' : 'gear listings';
|
||||||
|
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
return `No ${label} listed there yet 🦧 — be the first to add some at [skate-spots.org](https://skate-spots.org).`;
|
// Be honest: an empty result can mean "none published" OR that the relays
|
||||||
|
// were slow/unreachable OR a misspelled place. Don't assert "not added yet"
|
||||||
|
// as fact — offer both the add path and a retry.
|
||||||
|
const place =
|
||||||
|
'location' in intent ? intent.location :
|
||||||
|
'search' in intent ? intent.search : '';
|
||||||
|
const where = place ? ` in **${place}**` : '';
|
||||||
|
return `Didn't find any ${label}${where} right now 🦧\n\nThat could mean none are listed yet — or the relays were slow to answer. Try again in a moment, double-check the spelling, or [add the first one on the map](https://skate-spots.org/map). 🛹`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show a generous slice, then honestly summarise the rest so the headline
|
// Show a generous slice, then honestly summarise the rest so the headline
|
||||||
|
|
@ -1076,11 +1083,12 @@ export function SasquatchGuide({ message, onDismiss }: SasquatchGuideProps) {
|
||||||
|
|
||||||
if (toolCall) {
|
if (toolCall) {
|
||||||
const { tool: toolName, args: toolArgs } = toolCall;
|
const { tool: toolName, args: toolArgs } = toolCall;
|
||||||
setStreamingText(`🔍 Querying CVM for ${toolName.replace(/_/g, ' ')}...`);
|
setStreamingText(`🔍 Searching the relays for ${toolName.replace(/_/g, ' ')}...`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Call the live CVM server — it aggregates across all relays, deduplicates,
|
// Query the Nostr relays directly (useCVMClient) — aggregates across
|
||||||
// and runs complex feature filters that plain Nostr queries can't do
|
// the community relays, deduplicates, and runs feature filters that
|
||||||
|
// plain Nostr filters can't do. No server round-trip.
|
||||||
let toolResult: unknown;
|
let toolResult: unknown;
|
||||||
switch (toolName) {
|
switch (toolName) {
|
||||||
case 'find_spots':
|
case 'find_spots':
|
||||||
|
|
@ -1168,7 +1176,7 @@ Live data from ${toolName}:
|
||||||
${toolResultStr}
|
${toolResultStr}
|
||||||
|
|
||||||
Instructions:
|
Instructions:
|
||||||
- If count is 0: say nothing found yet, encourage them to add content at skate-spots.org
|
- If count is 0: say you didn't find any right now — it may be none listed yet OR the relays were slow; suggest a quick retry and adding content at skate-spots.org (don't claim as fact that nobody has added one)
|
||||||
- If count > 0: list results as bullet points with name, brief description, and url link
|
- If count > 0: list results as bullet points with name, brief description, and url link
|
||||||
- Keep it short and skater-friendly
|
- Keep it short and skater-friendly
|
||||||
- Never make up data not in the results above`,
|
- Never make up data not in the results above`,
|
||||||
|
|
@ -1177,8 +1185,8 @@ Instructions:
|
||||||
}
|
}
|
||||||
} catch (toolErr) {
|
} catch (toolErr) {
|
||||||
setCvmConnected(false);
|
setCvmConnected(false);
|
||||||
console.warn('[Sasquatch] CVM call failed:', toolErr);
|
console.warn('[Sasquatch] relay query failed:', toolErr);
|
||||||
accumulated = "Couldn't reach the live data server right now. Try again in a moment! The CVM server aggregates spots from all relays — it should be back shortly. 🛹";
|
accumulated = "The relays didn't answer in time right now 🛰️ — that's usually a slow connection, not a missing spot. Give it another go in a moment. 🛹";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// No tool call — AI answered directly from knowledge
|
// No tool call — AI answered directly from knowledge
|
||||||
|
|
@ -1199,9 +1207,9 @@ Instructions:
|
||||||
|
|
||||||
if (!aiSucceeded) {
|
if (!aiSucceeded) {
|
||||||
// ── No-AI fallback #1: try to serve live data locally ──────────────
|
// ── No-AI fallback #1: try to serve live data locally ──────────────
|
||||||
// Parse the intent + location ourselves and call CVM directly. This is
|
// Parse the intent + location ourselves and query the relays directly.
|
||||||
// the big free-tier win: "spots in barcelona" still works even when
|
// This is the big free-tier win: "spots in barcelona" still works even
|
||||||
// both AI providers are rate-limited.
|
// when both AI providers are rate-limited.
|
||||||
const intent = parseLocalIntent(question);
|
const intent = parseLocalIntent(question);
|
||||||
|
|
||||||
// Trip planning gets its own rich-card path (not a text blob).
|
// Trip planning gets its own rich-card path (not a text blob).
|
||||||
|
|
@ -1640,17 +1648,15 @@ Instructions:
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<p className="text-[9px] text-gray-600 flex items-center gap-1 flex-wrap">
|
<p className="text-[9px] text-gray-600 flex items-center gap-1 flex-wrap">
|
||||||
Powered by{' '}
|
Live data from{' '}
|
||||||
<a
|
<a
|
||||||
href="https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530"
|
href="https://goskatespots.nostr1.com"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener noreferrer"
|
rel="noopener noreferrer"
|
||||||
className="text-[#ccff00]/60 hover:text-[#ccff00] underline underline-offset-1 transition-colors"
|
className="text-[#ccff00]/60 hover:text-[#ccff00] underline underline-offset-1 transition-colors"
|
||||||
>
|
>
|
||||||
ContextVM
|
Nostr relays
|
||||||
</a>
|
</a>
|
||||||
{' & '}
|
|
||||||
Nostr Relays Data
|
|
||||||
</p>
|
</p>
|
||||||
{chatMessages.length > 0 && (
|
{chatMessages.length > 0 && (
|
||||||
<button
|
<button
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ Version: v0.1.0`;
|
||||||
{ label: 'Powered by', name: 'Nostr', url: 'https://nostr.com', desc: 'Decentralized protocol' },
|
{ label: 'Powered by', name: 'Nostr', url: 'https://nostr.com', desc: 'Decentralized protocol' },
|
||||||
{ label: 'Crowdfunding', name: 'Angor', url: 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z', desc: 'Decentralized Bitcoin crowdfunding' },
|
{ label: 'Crowdfunding', name: 'Angor', url: 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z', desc: 'Decentralized Bitcoin crowdfunding' },
|
||||||
{ label: 'Distributed on', name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: 'Open-source app store' },
|
{ label: 'Distributed on', name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: 'Open-source app store' },
|
||||||
{ label: 'AI powered by', name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: 'Nostr AI infrastructure' },
|
{ label: 'Trust & crew chat via', name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: 'Relatr trust scores & encrypted crew-chat coordination' },
|
||||||
].map((item) => (
|
].map((item) => (
|
||||||
<a
|
<a
|
||||||
key={item.name}
|
key={item.name}
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ export default function CreateSpotPage() {
|
||||||
.slice(0, 50) + '-' + Date.now().toString(36);
|
.slice(0, 50) + '-' + Date.now().toString(36);
|
||||||
|
|
||||||
// Reverse geocode to get structured city/country tags
|
// Reverse geocode to get structured city/country tags
|
||||||
// This powers the CVM server's city/country filtering
|
// These power Sasquatch's city/country filtering (relay queries)
|
||||||
let cityTag = '';
|
let cityTag = '';
|
||||||
let countryTag = '';
|
let countryTag = '';
|
||||||
let locationStr = location.trim();
|
let locationStr = location.trim();
|
||||||
|
|
@ -308,7 +308,7 @@ export default function CreateSpotPage() {
|
||||||
eventTags.push(['location', locationStr]);
|
eventTags.push(['location', locationStr]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Structured city/country tags — critical for CVM server search
|
// Structured city/country tags — critical for Sasquatch relay search
|
||||||
if (cityTag) {
|
if (cityTag) {
|
||||||
eventTags.push(['city', cityTag]);
|
eventTags.push(['city', cityTag]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue