"use client" import { useIsMobile } from "@hooks/use-mobile" import { Collapsible, CollapsibleContent, CollapsibleTrigger, } from "@ui/components/collapsible" import { ChevronDown, ChevronRight } from "lucide-react" import { memo, useEffect, useState } from "react" import type { GraphEdge, GraphNode, LegendProps } from "./types" import { cn } from "@lib/utils" // Cookie utility functions for legend state const setCookie = (name: string, value: string, days = 365) => { if (typeof document === "undefined") return const expires = new Date() expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000) document.cookie = `${name}=${value};expires=${expires.toUTCString()};path=/` } const getCookie = (name: string): string | null => { if (typeof document === "undefined") return null const nameEQ = `${name}=` const ca = document.cookie.split(";") for (let i = 0; i < ca.length; i++) { let c = ca[i] if (!c) continue while (c.charAt(0) === " ") c = c.substring(1, c.length) if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length) } return null } interface ExtendedLegendProps extends LegendProps { id?: string nodes?: GraphNode[] edges?: GraphEdge[] isLoading?: boolean } // Toggle switch component matching Figma design const SmallToggle = memo(function SmallToggle({ checked, onChange, }: { checked: boolean onChange: (checked: boolean) => void }) { return ( ) }) // Hexagon SVG for memory nodes const HexagonIcon = memo(function HexagonIcon({ fill = "#0D2034", stroke = "#3B73B8", opacity = 1, size = 12, }: { fill?: string stroke?: string opacity?: number size?: number }) { return ( ) }) // Document icon (rounded square) const DocumentIcon = memo(function DocumentIcon() { return (
) }) // Connection icon (graph) const ConnectionIcon = memo(function ConnectionIcon() { return ( ) }) // Line icon for connections const LineIcon = memo(function LineIcon({ color, dashed = false, }: { color: string dashed?: boolean }) { return (
) }) // Similarity circle icon const SimilarityCircle = memo(function SimilarityCircle({ variant, }: { variant: "strong" | "weak" }) { return (
) }) // Accordion row with count const StatRow = memo(function StatRow({ icon, label, count, expandable = false, expanded = false, onToggle, children, }: { icon: React.ReactNode label: string count: number expandable?: boolean expanded?: boolean onToggle?: () => void children?: React.ReactNode }) { return (
{expandable && expanded && children && (
{children}
)}
) }) // Toggle row for relations/similarity const ToggleRow = memo(function ToggleRow({ icon, label, checked, onChange, }: { icon: React.ReactNode label: string checked: boolean onChange: (checked: boolean) => void }) { return (
{icon} {label}
) }) export const Legend = memo(function Legend({ variant: _variant = "console", id, nodes = [], edges = [], isLoading: _isLoading = false, }: ExtendedLegendProps) { const isMobile = useIsMobile() const [isExpanded, setIsExpanded] = useState(false) const [isInitialized, setIsInitialized] = useState(false) // Toggle states for relations const [showUpdates, setShowUpdates] = useState(true) const [showExtends, setShowExtends] = useState(true) const [showInferences, setShowInferences] = useState(false) // Toggle states for similarity const [showStrong, setShowStrong] = useState(true) const [showWeak, setShowWeak] = useState(true) // Expanded accordion states const [memoriesExpanded, setMemoriesExpanded] = useState(false) const [documentsExpanded, setDocumentsExpanded] = useState(false) const [connectionsExpanded, setConnectionsExpanded] = useState(true) // Load saved preference on client side useEffect(() => { if (!isInitialized) { const savedState = getCookie("legendCollapsed") if (savedState === "true") { setIsExpanded(false) } else if (savedState === "false") { setIsExpanded(true) } else { // Default: collapsed on mobile, collapsed on desktop too (per Figma) setIsExpanded(false) } setIsInitialized(true) } }, [isInitialized]) // Save to cookie when state changes const handleToggleExpanded = (expanded: boolean) => { setIsExpanded(expanded) setCookie("legendCollapsed", expanded ? "false" : "true") } // Calculate stats const memoryCount = nodes.filter((n) => n.type === "memory").length const documentCount = nodes.filter((n) => n.type === "document").length const connectionCount = edges.length // Hide on mobile if (isMobile) return null return (
{/* Glass background */}
{/* Header - always visible */} {isExpanded ? ( ) : ( )} Legend
{/* Main content column */}
{/* STATISTICS Section */}
STATISTICS
{/* Memories */} } label="Memories" count={memoryCount} expandable expanded={memoriesExpanded} onToggle={() => setMemoriesExpanded(!memoriesExpanded)} >
Memory (latest)
76
Memory (oldest)
182
Score
23
New memory
17
Expiring soon
11
Forgotten
6
{/* Documents */} } label="Documents" count={documentCount} expandable expanded={documentsExpanded} onToggle={() => setDocumentsExpanded(!documentsExpanded)} /> {/* Connections */} } label="Connections" count={connectionCount} expandable expanded={connectionsExpanded} onToggle={() => setConnectionsExpanded(!connectionsExpanded) } >
Doc > Memory
} label="Doc similarity" checked={showStrong} onChange={setShowStrong} />
{/* RELATIONS Section */}
RELATIONS
} label="Updates" checked={showUpdates} onChange={setShowUpdates} /> } label="Extends" checked={showExtends} onChange={setShowExtends} /> } label="Inferences" checked={showInferences} onChange={setShowInferences} />
{/* SIMILARITY Section */}
SIMILARITY
} label="Strong" checked={showStrong} onChange={setShowStrong} /> } label="Weak" checked={showWeak} onChange={setShowWeak} />
{/* Scrollbar indicator */}
) }) Legend.displayName = "Legend"