"use client"; import { Popover, PopoverContent, PopoverTrigger } from "../ui/popover"; import type { NodePopupContent, EdgePopupContent } from "./type"; import { getNodeColor } from "./node-colors"; import { useMemo } from "react"; import { useTheme } from "remix-themes"; import dayjs from "dayjs"; /** * Format a date string into a readable format */ export function formatDate( dateString?: string | null, format: string = "MMM D, YYYY", ): string { if (!dateString) return "Unknown"; try { return dayjs(dateString).format(format); } catch (error) { console.error("Error formatting date:", error); return "Invalid date"; } } interface GraphPopoversProps { showNodePopup: boolean; showEdgePopup: boolean; nodePopupContent: NodePopupContent | null; edgePopupContent: EdgePopupContent | null; onOpenChange?: (open: boolean) => void; labelColorMap?: Map; } export function GraphPopovers({ showNodePopup, showEdgePopup, nodePopupContent, edgePopupContent, onOpenChange, labelColorMap, }: GraphPopoversProps) { const [resolvedTheme] = useTheme(); const isDarkMode = resolvedTheme === "dark"; const primaryNodeLabel = useMemo((): string | null => { if (!nodePopupContent) { return null; } // Check if node has primaryLabel property (GraphNode) const nodeAny = nodePopupContent.node as any; if (nodeAny.primaryLabel && typeof nodeAny.primaryLabel === "string") { return nodeAny.primaryLabel; } // Fall back to original logic with labels const primaryLabel = nodePopupContent.node.labels?.find( (label) => label !== "Entity", ); return primaryLabel || "Entity"; }, [nodePopupContent]); // Get the color for the primary label const labelColor = useMemo(() => { if (!primaryNodeLabel || !labelColorMap) return ""; return getNodeColor(primaryNodeLabel, isDarkMode, labelColorMap); }, [primaryNodeLabel, isDarkMode, labelColorMap]); const attributesToDisplay = useMemo(() => { if (!nodePopupContent) { return []; } const entityProperties = Object.fromEntries( Object.entries(nodePopupContent.node.attributes || {}).filter(([key]) => { return key !== "labels" && !key.includes("Embedding"); }), ); return Object.entries(entityProperties).map(([key, value]) => ({ key, value, })); }, [nodePopupContent]); return (
e.preventDefault()} >

Node Details

{primaryNodeLabel && ( {primaryNodeLabel} )}

Name: {nodePopupContent?.node.name || "Unknown"}

UUID: {nodePopupContent?.node.uuid || "Unknown"}

Created: {nodePopupContent?.node.created_at && formatDate(nodePopupContent?.node.created_at)}

{attributesToDisplay.length > 0 && (

Properties:

{attributesToDisplay.map(({ key, value }) => (

{key}: {" "} {typeof value === "object" ? JSON.stringify(value) : String(value)}

))}
)} {nodePopupContent?.node.summary && (

Summary:

{ e.stopPropagation(); const target = e.currentTarget; target.scrollTop += e.deltaY; }} >

{nodePopupContent.node.summary}

)}
e.preventDefault()} >

{edgePopupContent?.source.name || "Unknown"} →{" "} {edgePopupContent?.relation.name || "Unknown"} {" "} → {edgePopupContent?.target.name || "Unknown"}

Relationship

UUID: {edgePopupContent?.relation.uuid || "Unknown"}

Type: {edgePopupContent?.relation.name || "Unknown"}

{edgePopupContent?.relation.fact && (

Fact: {edgePopupContent.relation.fact}

)} {edgePopupContent?.relation.episodes?.length ? (

Episodes:

{edgePopupContent.relation.episodes.map((episode) => ( {episode} ))}
) : null}

Created: {formatDate(edgePopupContent?.relation.created_at)}

{edgePopupContent?.relation.valid_at && (

Valid From: {formatDate(edgePopupContent.relation.valid_at)}

)} {edgePopupContent?.relation.expired_at && (

Expired At: {formatDate(edgePopupContent.relation.expired_at)}

)} {edgePopupContent?.relation.invalid_at && (

Invalid At: {formatDate(edgePopupContent.relation.invalid_at)}

)}
); }