"use client"; import clsx from "clsx"; import { BookOpen, ChevronDown, ExternalLink, Globe, GraduationCap, Info, Library, Link as LinkIcon, SquarePen, Wrench, } from "lucide-react"; import { useEffect, useLayoutEffect, useRef, useState, type ComponentType, type SVGProps, } from "react"; import { createPortal } from "react-dom"; import type { TutorSource, TutorTool } from "@/lib/api"; type SourceSidebarProps = { onNewChat: () => void; onToggleSource: (sourceKey: string) => void; onToggleTool: (toolKey: string) => void; selectedSourceKeys: string[]; enabledToolKeys: string[]; sourceError: string | null; tools: TutorTool[]; }; type ToggleToolMeta = { icon: ComponentType>; description?: string; }; const POPOVER_GAP = 6; const TOGGLE_TOOL_META: Record = { web_search: { icon: Globe, description: "Live web search for recent events or facts outside the knowledge base.", }, url_context: { icon: LinkIcon, description: "Reads a specific URL you paste in the chat so the tutor can answer from its content.", }, web_fetch: { icon: LinkIcon, description: "Reads a specific URL you paste in the chat so the tutor can answer from its content.", }, }; export function SourceSidebar({ onNewChat, onToggleSource, onToggleTool, selectedSourceKeys, enabledToolKeys, sourceError, tools, }: SourceSidebarProps) { const retrievalTool = tools.find( (tool): tool is Extract => tool.kind === "configurable", ); const toggleTools = tools.filter( (tool): tool is Extract => tool.kind === "toggle", ); const activeCount = (retrievalTool && selectedSourceKeys.length > 0 ? 1 : 0) + toggleTools.filter((tool) => enabledToolKeys.includes(tool.key)).length; const totalCount = (retrievalTool ? 1 : 0) + toggleTools.length; const [openToolInfoKey, setOpenToolInfoKey] = useState(null); const [isRetrievalOpen, setIsRetrievalOpen] = useState(true); const toggleToolsRef = useRef(null); useEffect(() => { if (openToolInfoKey === null) return; function onDocMouseDown(event: MouseEvent) { if ( toggleToolsRef.current && !toggleToolsRef.current.contains(event.target as Node) ) { setOpenToolInfoKey(null); } } function onKey(event: KeyboardEvent) { if (event.key === "Escape") setOpenToolInfoKey(null); } document.addEventListener("mousedown", onDocMouseDown); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDocMouseDown); document.removeEventListener("keydown", onKey); }; }, [openToolInfoKey]); return ( ); } function RetrievalTool({ isOpen, onOpenChange, tool, selectedSourceKeys, onToggleSource, }: { isOpen: boolean; onOpenChange: (next: boolean) => void; tool: Extract; selectedSourceKeys: string[]; onToggleSource: (sourceKey: string) => void; }) { const courseSources = tool.sources.filter((source) => source.group === "courses"); const docSources = tool.sources.filter((source) => source.group === "docs"); const enabled = selectedSourceKeys.length > 0; const [infoOpen, setInfoOpen] = useState(false); const infoRef = useRef(null); const [dialogPos, setDialogPos] = useState< { top: number; left: number; width: number; placement: "above" | "below" } | null >(null); useEffect(() => { if (!infoOpen) return; function onDocMouseDown(event: MouseEvent) { const target = event.target as Node | null; const insideTrigger = !!infoRef.current && !!target && infoRef.current.contains(target); const insidePortaledDialog = !!target && target instanceof Element && !!target.closest('[data-knowledge-base-popover="true"]'); if ( !insideTrigger && !insidePortaledDialog ) { setInfoOpen(false); } } function onKey(event: KeyboardEvent) { if (event.key === "Escape") setInfoOpen(false); } document.addEventListener("mousedown", onDocMouseDown); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDocMouseDown); document.removeEventListener("keydown", onKey); }; }, [infoOpen]); useLayoutEffect(() => { if (!infoOpen || !infoRef.current) { return; } function recompute() { if (!infoRef.current) return; const trigger = infoRef.current.getBoundingClientRect(); const width = 224; const estimatedHeight = 150; const spaceBelow = window.innerHeight - trigger.bottom; const openAbove = spaceBelow < estimatedHeight + 16 && trigger.top > spaceBelow; setDialogPos({ top: openAbove ? trigger.top : trigger.bottom + POPOVER_GAP, left: Math.max(12, trigger.right - width), width, placement: openAbove ? "above" : "below", }); } recompute(); window.addEventListener("resize", recompute); window.addEventListener("scroll", recompute, true); return () => { window.removeEventListener("resize", recompute); window.removeEventListener("scroll", recompute, true); }; }, [infoOpen]); return (
{infoOpen && dialogPos && typeof document !== "undefined" ? createPortal(

Grounds answers in the sources you select using hybrid retrieval: semantic and keyword (BM25) search, then reranking. The tutor can also browse the knowledge base like a filesystem to read full documents before answering.

, document.body, ) : null} {isOpen ? (
{courseSources.length > 0 ? ( ) : null} {docSources.length > 0 ? ( ) : null}
) : null}
); } function SourceGroup({ label, icon: Icon, sources, selectedSourceKeys, onToggleSource, }: { label: string; icon: ComponentType>; sources: TutorSource[]; selectedSourceKeys: string[]; onToggleSource: (sourceKey: string) => void; }) { const [openPopoverKey, setOpenPopoverKey] = useState(null); const groupRef = useRef(null); useEffect(() => { if (openPopoverKey === null) return; function onDocMouseDown(event: MouseEvent) { const target = event.target as Node | null; const insideGroup = !!groupRef.current && !!target && groupRef.current.contains(target); const insidePortaledDialog = !!target && target instanceof Element && !!target.closest('[data-source-popover="true"]'); if (!insideGroup && !insidePortaledDialog) { setOpenPopoverKey(null); } } function onKey(event: KeyboardEvent) { if (event.key === "Escape") setOpenPopoverKey(null); } document.addEventListener("mousedown", onDocMouseDown); document.addEventListener("keydown", onKey); return () => { document.removeEventListener("mousedown", onDocMouseDown); document.removeEventListener("keydown", onKey); }; }, [openPopoverKey]); return (

{label}

{sources.map((source) => ( setOpenPopoverKey((current) => current === source.key ? null : source.key, ) } /> ))}
); } type PopoverInfo = { description: string; linkUrl: string; linkLabel: string; meta?: string; }; function formatIndexedMonth(isoDate: string | null | undefined): string | null { if (!isoDate) return null; const date = new Date(isoDate); if (Number.isNaN(date.getTime())) return null; return date.toLocaleString("en-US", { month: "short", year: "numeric" }); } function getPopoverInfo(source: TutorSource): PopoverInfo | undefined { // Description and link come from the registry via /api/tools; the frontend // renders them verbatim so adding a source needs no UI edit. if (!source.description || !source.infoUrl) return undefined; if (source.group === "courses") { return { description: source.description, linkUrl: source.infoUrl, linkLabel: "View on Academy", }; } const indexedMonth = formatIndexedMonth(source.indexedAt); const metaParts = [source.version, indexedMonth ? `indexed ${indexedMonth}` : null] .filter((part): part is string => Boolean(part)); return { description: source.description, linkUrl: source.infoUrl, linkLabel: "View docs", meta: metaParts.length > 0 ? metaParts.join(" · ") : undefined, }; } function SourceRow({ source, selected, onToggle, popoverOpen, onPopoverToggle, }: { source: TutorSource; selected: boolean; onToggle: (sourceKey: string) => void; popoverOpen: boolean; onPopoverToggle: () => void; }) { const isCourse = source.group === "courses"; const popover = getPopoverInfo(source); const rowRef = useRef(null); const [dialogPos, setDialogPos] = useState< { top: number; left: number; width: number; placement: "above" | "below" } | null >(null); useLayoutEffect(() => { if (!popoverOpen || !rowRef.current) { return; } function recompute() { if (!rowRef.current) return; const row = rowRef.current.getBoundingClientRect(); const estimatedHeight = 140; const spaceBelow = window.innerHeight - row.bottom; const openAbove = spaceBelow < estimatedHeight + 16 && row.top > spaceBelow; setDialogPos({ top: openAbove ? row.top : row.bottom + POPOVER_GAP, left: row.left, width: row.width, placement: openAbove ? "above" : "below", }); } recompute(); window.addEventListener("resize", recompute); window.addEventListener("scroll", recompute, true); return () => { window.removeEventListener("resize", recompute); window.removeEventListener("scroll", recompute, true); }; }, [popoverOpen]); return (
{popover ? ( ) : ( )}
{popover && popoverOpen && dialogPos && typeof document !== "undefined" ? createPortal(

{popover.description}

{popover.meta ? (

{popover.meta}

) : null} {popover.linkLabel}
, document.body, ) : null}
); } function ToggleToolRow({ tool, enabled, onToggle, infoOpen, onInfoToggle, }: { tool: Extract; enabled: boolean; onToggle: () => void; infoOpen: boolean; onInfoToggle: () => void; }) { const meta = TOGGLE_TOOL_META[tool.key]; const Icon = meta?.icon ?? Globe; const description = meta?.description; const rowRef = useRef(null); const [dialogPos, setDialogPos] = useState< { top: number; left: number; width: number; placement: "above" | "below" } | null >(null); useLayoutEffect(() => { if (!infoOpen || !rowRef.current) { return; } function recompute() { if (!rowRef.current) return; const row = rowRef.current.getBoundingClientRect(); const estimatedHeight = 92; const spaceAbove = row.top; const openAbove = spaceAbove > estimatedHeight + 12; setDialogPos({ top: openAbove ? row.top : row.bottom + POPOVER_GAP, left: row.left, width: row.width, placement: openAbove ? "above" : "below", }); } recompute(); window.addEventListener("resize", recompute); window.addEventListener("scroll", recompute, true); return () => { window.removeEventListener("resize", recompute); window.removeEventListener("scroll", recompute, true); }; }, [infoOpen]); return (
{description ? ( ) : ( )}
{description && infoOpen && dialogPos && typeof document !== "undefined" ? createPortal(

{description}

, document.body, ) : null}
); }