"use client"; import { motion, AnimatePresence } from "framer-motion"; import { Sparkles, ArrowRight, X, Minus, Database, Maximize2, Minimize2 } from "lucide-react"; 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 ── import MarkdownRenderer from "./MarkdownRenderer"; import EnergySavingsCalculator from "./EnergySavingsCalculator"; import ProcessComparisonTable from "./ProcessComparisonTable"; import RFTechExplainer from "./RFTechExplainer"; import ConsultationScheduler from "./ConsultationScheduler"; import CaseStudyViewer from "./CaseStudyViewer"; import EquipmentConfigurator from "./EquipmentConfigurator"; import EfficiencyCard from "./EfficiencyCard"; import { getAiSessionId } from "@/lib/aiSessionId"; export default function SilentObserver() { const { isAiExpanded, toggleAi, setAiExpanded, currentSection, activeApplicationTab, setActiveApplicationTab, 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); const scrollRef = useRef(null); // Refs for dynamic body (accessed inside transport function) const sectionRef = useRef(currentSection); const tabRef = useRef(activeApplicationTab); useEffect(() => { sectionRef.current = currentSection; }, [currentSection]); useEffect(() => { tabRef.current = activeApplicationTab; }, [activeApplicationTab]); useEffect(() => { const checkTheme = () => setIsDark(document.documentElement.classList.contains("dark")); checkTheme(); const observer = new MutationObserver(checkTheme); observer.observe(document.documentElement, { attributes: true, attributeFilter: ["class"] }); return () => observer.disconnect(); }, []); const handleClose = () => { setAiExpanded(false); setTimeout(() => setIsWideMode(false), 400); }; // ═══ AI SDK 6: Transport with dynamic body ═══ // sessionId is stable per visitor (localStorage UUID) so the chat route can // stitch all messages into the same AiConversation row for analytics. const transport = useMemo(() => new DefaultChatTransport({ api: "/api/chat", body: () => ({ sessionId: getAiSessionId(), locale, pageUrl: typeof window !== "undefined" ? window.location.href : null, context: { section: sectionRef.current, activeTab: tabRef.current, }, }), }), [locale]); // ═══ AI SDK 6: useChat ═══ const { messages, sendMessage, addToolOutput, status } = useChat({ transport, sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls, async onToolCall({ toolCall }) { if (toolCall.dynamic) return; if (toolCall.toolName === "navigate_to_section") { const { section, url, subAction, tabId, nodeId } = toolCall.input as { section?: string; url?: string; subAction?: string; tabId?: string; nodeId?: string; }; handleClose(); // Valid homepage DOM IDs — anything else is a page route const HOMEPAGE_IDS = new Set([ "technology", "applications-dashboard", "applications-deep", "global", "our-story", "legacy", ]); // Fallback map: if the AI sends a section name that's actually a page const SECTION_TO_PAGE: Record = { news: "/news", heritage: "/heritage", parts: "/parts", "parts-catalog": "/parts", contact: "/parts", "inside-flux": "/news", "spare-parts": "/parts", }; // Resolve: explicit url > section-to-page fallback > homepage scroll const resolvedUrl = url || (section && !HOMEPAGE_IDS.has(section) ? SECTION_TO_PAGE[section] || null : null); if (resolvedUrl) { // Cross-page navigation setTimeout(() => { router.push(`/${locale}${resolvedUrl}`); }, 400); addToolOutput({ tool: "navigate_to_section" as any, toolCallId: toolCall.toolCallId, output: `Navigated to page "${resolvedUrl}"`, }); } else if (section && HOMEPAGE_IDS.has(section)) { // Same-page scroll — only for confirmed homepage DOM IDs 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}"` : ""}`, }); } } }, }); const isLoading = status === "submitted" || status === "streaming"; useEffect(() => { if (scrollRef.current) scrollRef.current.scrollTop = scrollRef.current.scrollHeight; }, [messages]); useEffect(() => { const handler = (e: Event) => { const detail = (e as CustomEvent).detail; if (!isAiExpanded) setAiExpanded(true); setTimeout(() => { sendMessage({ text: `I'd like to schedule an engineering consultation${detail?.industry ? ` for my ${detail.industry} operation` : ""}${detail?.installation ? ` (reference: ${detail.installation})` : ""}. Please set that up.`, }); }, 500); }; window.addEventListener("flux:request-consultation", handler); return () => window.removeEventListener("flux:request-consultation", handler); }, [isAiExpanded, setAiExpanded, sendMessage]); useEffect(() => { const handler = (e: Event) => { const detail = (e as CustomEvent).detail; handleClose(); setTimeout(() => { const section = document.getElementById("global"); if (section) section.scrollIntoView({ behavior: "smooth" }); if (detail?.nodeId) { setSelectedMarkerId(detail.nodeId); setHighlightedMapNode(detail.nodeId); setTimeout(() => setHighlightedMapNode(null), 5000); } }, 400); }; window.addEventListener("flux:navigate-to-case", handler); return () => window.removeEventListener("flux:navigate-to-case", handler); }, [setSelectedMarkerId, setHighlightedMapNode]); useEffect(() => { const handler = (e: Event) => { const detail = (e as CustomEvent).detail; if (!detail?.nodeId) return; handleClose(); setTimeout(() => { const section = document.getElementById("global"); if (section) section.scrollIntoView({ behavior: "smooth" }); setSelectedMarkerId(detail.nodeId); }, 400); }; window.addEventListener("flux:open-case-study-modal", handler); return () => window.removeEventListener("flux:open-case-study-modal", handler); }, [setSelectedMarkerId]); useEffect(() => { const handler = (e: Event) => { const detail = (e as CustomEvent).detail; if (!isAiExpanded) setAiExpanded(true); setTimeout(() => { if (detail?.prompt) sendMessage({ text: detail.prompt }); }, 500); }; window.addEventListener("flux:trigger-ai", handler); return () => window.removeEventListener("flux:trigger-ai", handler); }, [isAiExpanded, setAiExpanded, sendMessage]); const onSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!input.trim() || isLoading) return; sendMessage({ text: input }); setInput(""); }; function renderToolPart(part: any, index: number) { const key = `tool-${index}`; const ToolLoading = ({ label }: { label: string }) => (
{label}
); const DataToolWorking = ({ label }: { label: string }) => (
{label}
); const ToolError = ({ msg }: { msg?: string }) => (
Analysis unavailable. {msg}
); if (part.type === "tool-recommend_application") { if (part.state === "input-streaming" || part.state === "input-available") return ; if (part.state === "output-available") return null; if (part.state === "output-error") return ; } if (part.type === "tool-search_installations") { if (part.state === "input-streaming" || part.state === "input-available") return ; if (part.state === "output-available") return null; if (part.state === "output-error") return ; } if (part.type === "tool-get_application_knowledge") { if (part.state === "input-streaming" || part.state === "input-available") return ; if (part.state === "output-available") return null; if (part.state === "output-error") return ; } if (part.type === "tool-show_case_study") { if (part.state === "input-streaming" || part.state === "input-available") return ; if (part.state === "output-available") { const o = (part as any).output; if (!o?.found) return null; return ; } if (part.state === "output-error") return ; } if (part.type === "tool-show_equipment_specs") { if (part.state === "input-streaming" || part.state === "input-available") return ; if (part.state === "output-available") { const o = (part as any).output; if (!o?.found) return null; return ; } if (part.state === "output-error") return ; } if (part.type === "tool-energy_savings_calculator") { if (part.state === "input-streaming" || part.state === "input-available") return ; if (part.state === "output-available") return ; if (part.state === "output-error") return ; } if (part.type === "tool-process_comparison_table") { if (part.state === "input-streaming" || part.state === "input-available") return ; if (part.state === "output-available") return ; if (part.state === "output-error") return ; } if (part.type === "tool-rf_technology_explainer") { if (part.state === "input-streaming" || part.state === "input-available") return ; if (part.state === "output-available") return ; if (part.state === "output-error") return ; } if (part.type === "tool-schedule_consultation") { if (part.state === "input-streaming" || part.state === "input-available") return ; if (part.state === "output-available") return ; if (part.state === "output-error") return ; } if (part.type === "tool-navigate_to_section") { if (part.state === "input-streaming" || part.state === "input-available") return
Navigating...
; if (part.state === "output-available") return Navigated to section; } if (part.type === "tool-show_efficiency_calculator") { if (part.state === "input-available" || part.state === "output-available") return ; } return null; } // ═══ Contextual Quick-Replies based on last assistant message ═══ function getContextualSuggestions(): string[] { if (isLoading || messages.length === 0) return []; const lastAssistant = [...messages].reverse().find(m => m.role === "assistant"); if (!lastAssistant?.parts) return []; const toolTypes = new Set( lastAssistant.parts .filter((p: any) => p.type?.startsWith("tool-") && p.state === "output-available") .map((p: any) => p.type) ); // Priority order: suggest the next logical funnel step if (toolTypes.has("tool-schedule_consultation")) return []; // End of funnel if (toolTypes.has("tool-show_equipment_specs")) return ["Schedule a consultation", "Compare with traditional methods"]; if (toolTypes.has("tool-show_case_study")) return ["Show me equipment specs", "Calculate savings for my operation", "Schedule a consultation"]; if (toolTypes.has("tool-energy_savings_calculator")) return ["Show me a real installation", "See equipment specs", "How does RF heating work?"]; if (toolTypes.has("tool-process_comparison_table")) return ["Calculate savings for my operation", "Show me proven installations"]; if (toolTypes.has("tool-rf_technology_explainer")) return ["What would I save in energy costs?", "Show me real installations"]; if (toolTypes.has("tool-recommend_application") || toolTypes.has("tool-get_application_knowledge")) return ["Calculate energy savings", "Show me case studies", "Compare RF vs my current method"]; if (toolTypes.has("tool-navigate_to_section")) return ["Tell me more about this", "How much energy can I save?"]; // Default: if assistant responded with text only (Stage 1 qualification) return []; } const suggestions = getContextualSuggestions(); return ( <> {isAiExpanded && ( )}
{!isAiExpanded ? (
Ask FluxAI ) : (
{isLoading && ()}
FluxAI Engineering Advisor
{messages.length > 0 && ( )}
{messages.length === 0 && (

FluxAI Engineering Advisor

Ask about energy savings, compare RF vs traditional methods, or let me guide you through our technology.

{["How much energy can I save?", "RF vs Hot Air", "How does RF heating work?", "Show me proven installations"].map((q) => ( ))}
)} {messages.map((m) => (
{m.parts?.map((part, index) => { if (part.type === "text" && part.text) { if (m.role === "user") return
{part.text}
; return
; } if (part.type?.startsWith("tool-")) return renderToolPart(part, index); if (part.type === "step-start" && index > 0) return
; return null; })}
))} {isLoading && messages.length > 0 && (
{[0, 1, 2].map((i) => ())}
)} {suggestions.length > 0 && !isLoading && ( {suggestions.map((s) => ( ))} )}
setInput(e.target.value)} placeholder="Ask about energy savings, RF technology..." className="flex-1 rounded-2xl border-none outline-none text-[13px] px-4 py-3 bg-black/[0.04] dark:bg-white/[0.06] text-[#1D1D1F] dark:text-[#F5F5F7] placeholder:text-[#86868B] dark:placeholder:text-[#A1A1A6]/60 focus:bg-black/[0.06] dark:focus:bg-white/[0.08] transition-colors duration-200" />
)}
); }