"use client"; import { useState, useEffect, useRef } from "react"; import { usePathname, useRouter } from "next/navigation"; import ChevronDownIcon from "../icons/chevron"; import CheckIcon from "../icons/check"; export type ReferenceVersion = "v1" | "v2"; const VERSION_OPTIONS: { value: ReferenceVersion; label: string }[] = [ { value: "v2", label: "v2 (Latest)" }, { value: "v1", label: "v1" }, ]; /** * Maps v1 page suffixes to their v2 equivalents and vice versa. * Suffix is the path after `/reference/vN/`, e.g. "hooks/useCopilotAction". * When switching versions, if the direct path doesn't exist in the target, * we look up the closest equivalent here, or fall back to the version root. */ const V1_TO_V2: Record = { "hooks/useCopilotAction": "hooks/useFrontendTool", "hooks/useCopilotReadable": "hooks/useAgentContext", "hooks/useCopilotAdditionalInstructions": "hooks/useAgentContext", "hooks/useCopilotChat": "hooks/useAgent", "hooks/useCopilotChatHeadless_c": "hooks/useAgent", "hooks/useCopilotChatSuggestions": "hooks/useConfigureSuggestions", "hooks/useCoAgent": "hooks/useAgent", "hooks/useCoAgentStateRender": "hooks/useRenderToolCall", "hooks/useDefaultTool": "hooks/useDefaultRenderTool", "hooks/useLangGraphInterrupt": "hooks/useHumanInTheLoop", "components/chat/CopilotChat": "components/CopilotChat", "components/chat/CopilotPopup": "components/CopilotPopup", "components/chat/CopilotSidebar": "components/CopilotSidebar", "components/chat": "components/CopilotChat", }; // Build the reverse mapping (v2 → v1) from the forward mapping. // For many-to-one mappings, the first entry wins. const V2_TO_V1: Record = {}; for (const [v1Path, v2Path] of Object.entries(V1_TO_V2)) { if (!(v2Path in V2_TO_V1)) { V2_TO_V1[v2Path] = v1Path; } } function resolveVersionPath( suffix: string, fromVersion: ReferenceVersion, toVersion: ReferenceVersion, ): string { const map = fromVersion === "v1" ? V1_TO_V2 : V2_TO_V1; // Exact match in the mapping → use the equivalent page if (suffix in map) { return `/reference/${toVersion}/${map[suffix]}`; } // Direct path exists in target (same page name in both versions) → keep it. // Pages that exist in both: hooks/useAgent, hooks/useFrontendTool, hooks/useHumanInTheLoop, // hooks/useRenderToolCall, components/CopilotKit, etc. // We can't check the filesystem from the client, so we maintain a set of // known pages per version and verify against that. // For simplicity, if it's not in the mapping, try the direct path — the // fallback below will catch pages that only exist in one version. const directPath = `/reference/${toVersion}/${suffix}`; // Pages that only exist in v1 (no v2 equivalent at all) const v1Only = new Set([ "classes/CopilotRuntime", "classes/CopilotTask", "classes/llm-adapters/OpenAIAdapter", "classes/llm-adapters/OpenAIAssistantAdapter", "classes/llm-adapters/AnthropicAdapter", "classes/llm-adapters/LangChainAdapter", "classes/llm-adapters/GoogleGenerativeAIAdapter", "classes/llm-adapters/GroqAdapter", "sdk/python/LangGraph", "sdk/python/LangGraphAgent", "sdk/python/CrewAI", "sdk/python/CrewAIAgent", "sdk/python/RemoteEndpoints", "sdk/js/LangGraph", "components/CopilotTextarea", ]); // Pages that only exist in v2 (no v1 equivalent) const v2Only = new Set([ "hooks/useAgentContext", "hooks/useSuggestions", "hooks/useConfigureSuggestions", "hooks/useCopilotKit", "hooks/useCopilotChatConfiguration", "components/CopilotChatView", "components/CopilotChatMessageView", "components/CopilotChatAssistantMessage", "components/CopilotChatUserMessage", "components/CopilotChatInput", ]); const blockedSet = toVersion === "v2" ? v1Only : v2Only; if (blockedSet.has(suffix)) { return `/reference/${toVersion}`; } return directPath; } interface VersionSelectorProps { onNavigate?: () => void; } export function getVersionFromPathname(pathname: string): ReferenceVersion { if (pathname.startsWith("/reference/v1")) return "v1"; return "v2"; } const VersionSelector = ({ onNavigate }: VersionSelectorProps) => { const [isOpen, setIsOpen] = useState(false); const pathname = usePathname(); const router = useRouter(); const dropdownRef = useRef(null); const currentVersion = getVersionFromPathname(pathname); // Close dropdown on outside click useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if ( dropdownRef.current && !dropdownRef.current.contains(event.target as Node) ) { setIsOpen(false); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, []); const handleVersionClick = (version: ReferenceVersion) => { setIsOpen(false); if (version === currentVersion) return; // Extract the page suffix after `/reference/vN/` const prefix = `/reference/${currentVersion}/`; const suffix = pathname.startsWith(prefix) ? pathname.slice(prefix.length) : ""; const newPath = suffix ? resolveVersionPath(suffix, currentVersion, version) : `/reference/${version}`; onNavigate?.(); router.push(newPath); }; const currentOption = VERSION_OPTIONS.find( (opt) => opt.value === currentVersion, )!; return (
setIsOpen(!isOpen)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { setIsOpen(!isOpen); } }} tabIndex={0} role="button" aria-label="Select API version" aria-expanded={isOpen} >
API
{currentOption.label}
{isOpen && (
{VERSION_OPTIONS.map(({ value, label }) => (
handleVersionClick(value)} onKeyDown={(e) => { if (e.key === "Enter" || e.key === " ") { handleVersionClick(value); } }} tabIndex={0} role="option" aria-selected={currentVersion === value} > {label} {currentVersion === value && ( )}
))}
)}
); }; export default VersionSelector;