diff --git a/src/app/api/chat/route.ts b/src/app/api/chat/route.ts index ea353f1..88df299 100644 --- a/src/app/api/chat/route.ts +++ b/src/app/api/chat/route.ts @@ -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. 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. -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. 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. @@ -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({ - 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({ - 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'), - 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'), }), }), diff --git a/src/components/ai/SilentObserver.tsx b/src/components/ai/SilentObserver.tsx index 8ee7656..848066b 100644 --- a/src/components/ai/SilentObserver.tsx +++ b/src/components/ai/SilentObserver.tsx @@ -5,6 +5,7 @@ import { Sparkles, ArrowRight, X, Minus, Database, Maximize2, Minimize2 } from " import { useChat } from "@ai-sdk/react"; import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from "ai"; import { useUIStore } from "@/lib/store/uiStore"; +import { useRouter, usePathname } from "next/navigation"; import { useState, useEffect, useRef, useMemo } from "react"; // ── Renderers ── @@ -24,6 +25,10 @@ export default function SilentObserver() { setHighlightedMapNode, setSelectedMarkerId, } = useUIStore(); + const router = useRouter(); + const pathname = usePathname(); + const locale = pathname?.split('/')[1] || 'en'; + const [input, setInput] = useState(""); const [isDark, setIsDark] = useState(false); const [isWideMode, setIsWideMode] = useState(false); @@ -68,24 +73,38 @@ export default function SilentObserver() { if (toolCall.dynamic) return; if (toolCall.toolName === "navigate_to_section") { - const { section, subAction, tabId, nodeId } = toolCall.input as { - section: string; subAction?: string; tabId?: string; nodeId?: string; + const { section, url, subAction, tabId, nodeId } = toolCall.input as { + section?: string; url?: string; subAction?: string; tabId?: string; nodeId?: string; }; handleClose(); - setTimeout(() => { - const el = document.getElementById(section); - if (el) el.scrollIntoView({ behavior: "smooth", block: "start" }); - if (subAction === "activate-tab" && tabId) setActiveApplicationTab(tabId); - if (subAction === "highlight-node" && nodeId) { - setHighlightedMapNode(nodeId); - setTimeout(() => setHighlightedMapNode(null), 5000); - } - }, 400); - addToolOutput({ - tool: "navigate_to_section" as any, - toolCallId: toolCall.toolCallId, - output: `Navigated to "${section}" section${tabId ? `, activated tab "${tabId}"` : ""}${nodeId ? `, highlighted node "${nodeId}"` : ""}`, - }); + + 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(() => { + const el = document.getElementById(section); + if (el) el.scrollIntoView({ behavior: "smooth", block: "start" }); + if (subAction === "activate-tab" && tabId) setActiveApplicationTab(tabId); + if (subAction === "highlight-node" && nodeId) { + setHighlightedMapNode(nodeId); + setTimeout(() => setHighlightedMapNode(null), 5000); + } + }, 400); + addToolOutput({ + tool: "navigate_to_section" as any, + toolCallId: toolCall.toolCallId, + output: `Navigated to "${section}" section${tabId ? `, activated tab "${tabId}"` : ""}${nodeId ? `, highlighted node "${nodeId}"` : ""}`, + }); + } } }, });