diff options
Diffstat (limited to 'packages/ui/memory-graph')
| -rw-r--r-- | packages/ui/memory-graph/hooks/use-graph-data.ts | 18 | ||||
| -rw-r--r-- | packages/ui/memory-graph/memory-graph.tsx | 64 | ||||
| -rw-r--r-- | packages/ui/memory-graph/navigation-controls.tsx | 97 |
3 files changed, 97 insertions, 82 deletions
diff --git a/packages/ui/memory-graph/hooks/use-graph-data.ts b/packages/ui/memory-graph/hooks/use-graph-data.ts index 3e9fa5cc..ec17a756 100644 --- a/packages/ui/memory-graph/hooks/use-graph-data.ts +++ b/packages/ui/memory-graph/hooks/use-graph-data.ts @@ -29,26 +29,26 @@ export function useGraphData( const allEdges: GraphEdge[] = []; // Filter documents that have memories in selected space - const filteredDocuments = data.documents + const filteredDocuments = (data.documents || []) .map((doc) => ({ ...doc, memoryEntries: selectedSpace === "all" - ? doc.memoryEntries - : doc.memoryEntries.filter( + ? doc.memoryEntries || [] + : (doc.memoryEntries || []).filter( (memory) => (memory.spaceContainerTag ?? memory.spaceId ?? "default") === selectedSpace, ), })) - .filter((doc) => doc.memoryEntries.length > 0); + .filter((doc) => (doc.memoryEntries || []).length > 0); // Group documents by space for better clustering const documentsBySpace = new Map<string, typeof filteredDocuments>(); filteredDocuments.forEach((doc) => { const docSpace = - doc.memoryEntries[0]?.spaceContainerTag ?? - doc.memoryEntries[0]?.spaceId ?? + (doc.memoryEntries || [])[0]?.spaceContainerTag ?? + (doc.memoryEntries || [])[0]?.spaceId ?? "default"; if (!documentsBySpace.has(docSpace)) { documentsBySpace.set(docSpace, []); @@ -171,7 +171,7 @@ export function useGraphData( const memoryNodeMap = new Map<string, GraphNode>(); const doc = docNode.data as DocumentWithMemories; - doc.memoryEntries.forEach((memory, memIndex) => { + (doc.memoryEntries || []).forEach((memory, memIndex) => { const memoryId = `${memory.id}`; const customMemPos = nodePositions.get(memoryId); @@ -231,8 +231,8 @@ export function useGraphData( }); // Add version-chain edges (old -> new) - data.documents.forEach((doc) => { - doc.memoryEntries.forEach((mem: MemoryEntry) => { + (data.documents || []).forEach((doc) => { + (doc.memoryEntries || []).forEach((mem: MemoryEntry) => { // Support both new object structure and legacy array/single parent fields let parentRelations: Record<string, MemoryRelation> = {}; diff --git a/packages/ui/memory-graph/memory-graph.tsx b/packages/ui/memory-graph/memory-graph.tsx index 912a741a..72ddf090 100644 --- a/packages/ui/memory-graph/memory-graph.tsx +++ b/packages/ui/memory-graph/memory-graph.tsx @@ -157,8 +157,8 @@ export const MemoryGraph = ({ const spaceSet = new Set<string>(); const counts: Record<string, number> = {}; - data.documents.forEach((doc) => { - doc.memoryEntries.forEach((memory) => { + (data.documents || []).forEach((doc) => { + (doc.memoryEntries || []).forEach((memory) => { const spaceId = memory.spaceContainerTag || memory.spaceId || "default"; spaceSet.add(spaceId); counts[spaceId] = (counts[spaceId] || 0) + 1; @@ -199,32 +199,47 @@ export const MemoryGraph = ({ const handleCenter = useCallback(() => { if (nodes.length > 0) { // Calculate center of all nodes - let sumX = 0 - let sumY = 0 - let count = 0 - + let sumX = 0; + let sumY = 0; + let count = 0; + nodes.forEach((node) => { - sumX += node.x - sumY += node.y - count++ - }) - + sumX += node.x; + sumY += node.y; + count++; + }); + if (count > 0) { - const centerX = sumX / count - const centerY = sumY / count - centerViewportOn(centerX, centerY, containerSize.width, containerSize.height) + const centerX = sumX / count; + const centerY = sumY / count; + centerViewportOn( + centerX, + centerY, + containerSize.width, + containerSize.height, + ); } } - }, [nodes, centerViewportOn, containerSize.width, containerSize.height]) + }, [nodes, centerViewportOn, containerSize.width, containerSize.height]); const handleAutoFit = useCallback(() => { - if (nodes.length > 0 && containerSize.width > 0 && containerSize.height > 0) { + if ( + nodes.length > 0 && + containerSize.width > 0 && + containerSize.height > 0 + ) { autoFitToViewport(nodes, containerSize.width, containerSize.height, { occludedRightPx, animate: true, - }) + }); } - }, [nodes, containerSize.width, containerSize.height, occludedRightPx, autoFitToViewport]) + }, [ + nodes, + containerSize.width, + containerSize.height, + occludedRightPx, + autoFitToViewport, + ]); // Get selected node data const selectedNodeData = useMemo(() => { @@ -251,7 +266,7 @@ export const MemoryGraph = ({ }; // Count visible documents - const visibleDocuments = data.documents.filter((doc) => { + const visibleDocuments = (data.documents || []).filter((doc) => { const docNodes = nodes.filter( (node) => node.type === "document" && node.data.id === doc.id, ); @@ -265,7 +280,8 @@ export const MemoryGraph = ({ }); // If 80% or more of documents are visible, load more - const visibilityRatio = visibleDocuments.length / data.documents.length; + const visibilityRatio = + visibleDocuments.length / (data.documents || []).length; if (visibilityRatio >= 0.8) { loadMoreDocuments(); } @@ -421,8 +437,12 @@ export const MemoryGraph = ({ {containerSize.width > 0 && ( <NavigationControls onCenter={handleCenter} - onZoomIn={() => zoomIn(containerSize.width / 2, containerSize.height / 2)} - onZoomOut={() => zoomOut(containerSize.width / 2, containerSize.height / 2)} + onZoomIn={() => + zoomIn(containerSize.width / 2, containerSize.height / 2) + } + onZoomOut={() => + zoomOut(containerSize.width / 2, containerSize.height / 2) + } onAutoFit={handleAutoFit} nodes={nodes} className="absolute bottom-4 left-4" diff --git a/packages/ui/memory-graph/navigation-controls.tsx b/packages/ui/memory-graph/navigation-controls.tsx index b2abd67f..afd98a0f 100644 --- a/packages/ui/memory-graph/navigation-controls.tsx +++ b/packages/ui/memory-graph/navigation-controls.tsx @@ -1,67 +1,62 @@ -"use client" +"use client"; -import { memo } from "react" -import type { GraphNode } from "./types" +import { memo } from "react"; +import type { GraphNode } from "./types"; interface NavigationControlsProps { - onCenter: () => void - onZoomIn: () => void - onZoomOut: () => void - onAutoFit: () => void - nodes: GraphNode[] - className?: string + onCenter: () => void; + onZoomIn: () => void; + onZoomOut: () => void; + onAutoFit: () => void; + nodes: GraphNode[]; + className?: string; } -export const NavigationControls = memo<NavigationControlsProps>(({ - onCenter, - onZoomIn, - onZoomOut, - onAutoFit, - nodes, - className = "", -}) => { - if (nodes.length === 0) { - return null - } +export const NavigationControls = memo<NavigationControlsProps>( + ({ onCenter, onZoomIn, onZoomOut, onAutoFit, nodes, className = "" }) => { + if (nodes.length === 0) { + return null; + } - return ( - <div className={`flex flex-col gap-1 ${className}`}> - <button - type="button" - onClick={onAutoFit} - className="bg-black/20 backdrop-blur-sm hover:bg-black/30 border border-white/10 hover:border-white/20 rounded-lg p-2 text-white/70 hover:text-white transition-colors text-xs font-medium min-w-16" - title="Auto-fit graph to viewport" - > - Fit - </button> - <button - type="button" - onClick={onCenter} - className="bg-black/20 backdrop-blur-sm hover:bg-black/30 border border-white/10 hover:border-white/20 rounded-lg p-2 text-white/70 hover:text-white transition-colors text-xs font-medium min-w-16" - title="Center view on graph" - > - Center - </button> - <div className="flex flex-col"> + return ( + <div className={`flex flex-col gap-1 ${className}`}> <button type="button" - onClick={onZoomIn} - className="bg-black/20 backdrop-blur-sm hover:bg-black/30 border border-white/10 hover:border-white/20 rounded-t-lg p-2 text-white/70 hover:text-white transition-colors text-xs font-medium min-w-16 border-b-0" - title="Zoom in" + onClick={onAutoFit} + className="bg-black/20 backdrop-blur-sm hover:bg-black/30 border border-white/10 hover:border-white/20 rounded-lg p-2 text-white/70 hover:text-white transition-colors text-xs font-medium min-w-16" + title="Auto-fit graph to viewport" > - + + Fit </button> <button type="button" - onClick={onZoomOut} - className="bg-black/20 backdrop-blur-sm hover:bg-black/30 border border-white/10 hover:border-white/20 rounded-b-lg p-2 text-white/70 hover:text-white transition-colors text-xs font-medium min-w-16" - title="Zoom out" + onClick={onCenter} + className="bg-black/20 backdrop-blur-sm hover:bg-black/30 border border-white/10 hover:border-white/20 rounded-lg p-2 text-white/70 hover:text-white transition-colors text-xs font-medium min-w-16" + title="Center view on graph" > - − + Center </button> + <div className="flex flex-col"> + <button + type="button" + onClick={onZoomIn} + className="bg-black/20 backdrop-blur-sm hover:bg-black/30 border border-white/10 hover:border-white/20 rounded-t-lg p-2 text-white/70 hover:text-white transition-colors text-xs font-medium min-w-16 border-b-0" + title="Zoom in" + > + + + </button> + <button + type="button" + onClick={onZoomOut} + className="bg-black/20 backdrop-blur-sm hover:bg-black/30 border border-white/10 hover:border-white/20 rounded-b-lg p-2 text-white/70 hover:text-white transition-colors text-xs font-medium min-w-16" + title="Zoom out" + > + − + </button> + </div> </div> - </div> - ) -}) + ); + }, +); -NavigationControls.displayName = "NavigationControls"
\ No newline at end of file +NavigationControls.displayName = "NavigationControls"; |