feat(ai): extend FluxAI navigation with cross-page routing

The navigate_to_section tool now supports two modes:
A) Same-page scroll — scrollIntoView to real homepage DOM IDs
   (technology, applications-dashboard, applications-deep, global,
   our-story, legacy)
B) Cross-page routing — router.push to /applications/{slug},
   /news, /heritage, /parts with automatic locale prefix.

Fixed: system prompt listed phantom section IDs (hero, news,
heritage, timeline, parts-catalog, contact) that don't exist in
the DOM — causing all non-homepage navigations to silently fail.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-06 19:38:06 -05:00
parent c45a5be99e
commit 95132476ae
2 changed files with 66 additions and 21 deletions
+31 -5
View File
@@ -114,7 +114,7 @@ TOOL USAGE RULES:
1. SEARCH INSTALLATIONS: Use 'search_installations' to find real installations from our database. This is a DATA tool — you receive the results and reason about them before responding. 1. SEARCH INSTALLATIONS: Use 'search_installations' to find real installations from our database. This is a DATA tool — you receive the results and reason about them before responding.
2. SHOW CASE STUDY: Use 'show_case_study' to display a rich case study card for a specific installation. Requires a nodeId (get it from search_installations first) or an application slug for auto-match. 2. SHOW CASE STUDY: Use 'show_case_study' to display a rich case study card for a specific installation. Requires a nodeId (get it from search_installations first) or an application slug for auto-match.
3. SAVINGS/ROI: Use 'energy_savings_calculator' when discussing costs, energy, ROI. If volume is missing, assume 500 kg/h and 16h/day. 3. SAVINGS/ROI: Use 'energy_savings_calculator' when discussing costs, energy, ROI. If volume is missing, assume 500 kg/h and 16h/day.
4. NAVIGATION: Use 'navigate_to_section' to move the user around the site. 4. NAVIGATION: Use 'navigate_to_section' to move the user around the site. Two modes: (A) "section" for homepage scroll — valid IDs: "technology", "applications-dashboard", "applications-deep", "global", "our-story", "legacy". (B) "url" for cross-page navigation — e.g. url="/applications/textile-drying", url="/news", url="/heritage", url="/parts". ALWAYS use mode B for news, heritage, parts, and application detail pages.
5. COMPARISONS: Use 'process_comparison_table' for RF vs conventional tech. 5. COMPARISONS: Use 'process_comparison_table' for RF vs conventional tech.
6. EXPLAIN TECH: Use 'rf_technology_explainer' for physics/mechanism questions. 6. EXPLAIN TECH: Use 'rf_technology_explainer' for physics/mechanism questions.
7. APPLICATION KNOWLEDGE: Use 'get_application_knowledge' to retrieve deep technical theory, advantages, and datasheets from our knowledge base. 7. APPLICATION KNOWLEDGE: Use 'get_application_knowledge' to retrieve deep technical theory, advantages, and datasheets from our knowledge base.
@@ -459,13 +459,39 @@ export async function POST(req: Request) {
}, },
}), }),
// ── TOOL 6: Navigate to Section (client-side — NO execute) ── // ── TOOL 6: Navigate (client-side — NO execute) ──────────────
// Handles BOTH same-page scrolling (section) and cross-page
// routing (url). The client inspects which field is set.
navigate_to_section: tool({ navigate_to_section: tool({
description: `Maps the user to a specific section of the FLUX website. Use when the user says "show me", "take me to", "where is", or asks about a specific page section. Available sections: "hero", "applications-dashboard", "applications-deep", "global" (globe), "timeline", "heritage", "news", "parts-catalog", "contact".`, description: `Navigate the user to any part of the FLUX website.
TWO MODES:
A) SAME-PAGE SCROLL — set "section" to scroll to a homepage element by its DOM id:
"technology" (hero/intro), "applications-dashboard", "applications-deep", "global" (interactive globe), "our-story" (timeline), "legacy" (Patrizio legacy)
ONLY use these exact IDs. They only work when the user is on the homepage.
B) CROSS-PAGE NAVIGATION — set "url" to a route path (WITHOUT locale prefix). The client adds the locale automatically:
"/news" — news hub listing
"/news/{slug}" — specific article (use a real slug from context)
"/heritage" — company heritage deep-dive
"/parts" — spare parts catalog (B2B portal)
"/applications/{slug}" — application detail page (use real slug from the database list above)
RULES:
- ALWAYS prefer mode B for news, heritage, parts, and application detail pages.
- Only use mode A for scrolling within the homepage.
- When using mode B, use application slugs from the database list in this prompt.
- "show me textile drying" → url="/applications/textile-drying"
- "take me to the news" → url="/news"
- "show me the heritage" → url="/heritage"
- "show me the spare parts" → url="/parts"
- "show me the globe" → section="global"
- "go to the top" → section="technology"`,
inputSchema: z.object({ inputSchema: z.object({
section: z.string().describe('Target section ID'), section: z.string().optional().describe('Homepage element ID for same-page scroll'),
url: z.string().optional().describe('Route path for cross-page navigation (e.g. "/applications/textile-drying", "/news", "/heritage"). No locale prefix.'),
subAction: z.enum(['none', 'activate-tab', 'highlight-node']).default('none'), subAction: z.enum(['none', 'activate-tab', 'highlight-node']).default('none'),
tabId: z.string().optional().describe('Application slug to activate'), tabId: z.string().optional().describe('Application slug to activate on dashboard tab'),
nodeId: z.string().optional().describe('Globe node ID to highlight'), nodeId: z.string().optional().describe('Globe node ID to highlight'),
}), }),
}), }),
+21 -2
View File
@@ -5,6 +5,7 @@ import { Sparkles, ArrowRight, X, Minus, Database, Maximize2, Minimize2 } from "
import { useChat } from "@ai-sdk/react"; import { useChat } from "@ai-sdk/react";
import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from "ai"; import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from "ai";
import { useUIStore } from "@/lib/store/uiStore"; import { useUIStore } from "@/lib/store/uiStore";
import { useRouter, usePathname } from "next/navigation";
import { useState, useEffect, useRef, useMemo } from "react"; import { useState, useEffect, useRef, useMemo } from "react";
// ── Renderers ── // ── Renderers ──
@@ -24,6 +25,10 @@ export default function SilentObserver() {
setHighlightedMapNode, setSelectedMarkerId, setHighlightedMapNode, setSelectedMarkerId,
} = useUIStore(); } = useUIStore();
const router = useRouter();
const pathname = usePathname();
const locale = pathname?.split('/')[1] || 'en';
const [input, setInput] = useState(""); const [input, setInput] = useState("");
const [isDark, setIsDark] = useState(false); const [isDark, setIsDark] = useState(false);
const [isWideMode, setIsWideMode] = useState(false); const [isWideMode, setIsWideMode] = useState(false);
@@ -68,10 +73,23 @@ export default function SilentObserver() {
if (toolCall.dynamic) return; if (toolCall.dynamic) return;
if (toolCall.toolName === "navigate_to_section") { if (toolCall.toolName === "navigate_to_section") {
const { section, subAction, tabId, nodeId } = toolCall.input as { const { section, url, subAction, tabId, nodeId } = toolCall.input as {
section: string; subAction?: string; tabId?: string; nodeId?: string; section?: string; url?: string; subAction?: string; tabId?: string; nodeId?: string;
}; };
handleClose(); handleClose();
if (url) {
// Mode B: Cross-page navigation
setTimeout(() => {
router.push(`/${locale}${url}`);
}, 400);
addToolOutput({
tool: "navigate_to_section" as any,
toolCallId: toolCall.toolCallId,
output: `Navigated to page "${url}"`,
});
} else if (section) {
// Mode A: Same-page scroll (existing behavior)
setTimeout(() => { setTimeout(() => {
const el = document.getElementById(section); const el = document.getElementById(section);
if (el) el.scrollIntoView({ behavior: "smooth", block: "start" }); if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
@@ -87,6 +105,7 @@ export default function SilentObserver() {
output: `Navigated to "${section}" section${tabId ? `, activated tab "${tabId}"` : ""}${nodeId ? `, highlighted node "${nodeId}"` : ""}`, output: `Navigated to "${section}" section${tabId ? `, activated tab "${tabId}"` : ""}${nodeId ? `, highlighted node "${nodeId}"` : ""}`,
}); });
} }
}
}, },
}); });