diff options
| author | MaheshtheDev <[email protected]> | 2026-01-21 03:11:53 +0000 |
|---|---|---|
| committer | MaheshtheDev <[email protected]> | 2026-01-21 03:11:53 +0000 |
| commit | 1423bd70041c8dc0d863c13f1377865c6c875181 (patch) | |
| tree | 29155642e31153d6873640aa5e4d8af5c45e213e /packages/memory-graph/src | |
| parent | feat: create space, delete spaces and emoji picker (#687) (diff) | |
| download | supermemory-1423bd70041c8dc0d863c13f1377865c6c875181.tar.xz supermemory-1423bd70041c8dc0d863c13f1377865c6c875181.zip | |
feat: mobile responsive, lint formats, toast, render issue fix (#688)01-20-feat_mobile_responsive_lint_formats_ui_improvements_render_issue_fix
- Mobile responsive
- new toast design
- web document render issue fix
- posthog analytics
- ui improvements
Diffstat (limited to 'packages/memory-graph/src')
8 files changed, 312 insertions, 159 deletions
diff --git a/packages/memory-graph/src/components/graph-canvas.tsx b/packages/memory-graph/src/components/graph-canvas.tsx index 28dd53c7..f4cede9f 100644 --- a/packages/memory-graph/src/components/graph-canvas.tsx +++ b/packages/memory-graph/src/components/graph-canvas.tsx @@ -84,11 +84,11 @@ export const GraphCanvas = memo<GraphCanvasProps>( const progress = Math.min(elapsed / duration, 1) // Ease-out cubic easing for smooth deceleration - const eased = 1 - Math.pow(1 - progress, 3) + const eased = 1 - (1 - progress) ** 3 dimProgress.current = startDim + (targetDim - startDim) * eased // Force re-render to update canvas during animation - forceRender(prev => prev + 1) + forceRender((prev) => prev + 1) if (progress < 1) { dimAnimationRef.current = requestAnimationFrame(animate) @@ -145,8 +145,10 @@ export const GraphCanvas = memo<GraphCanvasProps>( // Only check nodes in the clicked cell (and neighboring cells for edge cases) const cellsToCheck = [ cellKey, - `${cellX-1},${cellY}`, `${cellX+1},${cellY}`, - `${cellX},${cellY-1}`, `${cellX},${cellY+1}`, + `${cellX - 1},${cellY}`, + `${cellX + 1},${cellY}`, + `${cellX},${cellY - 1}`, + `${cellX},${cellY + 1}`, ] // Check from top-most to bottom-most: memory nodes are drawn after documents @@ -360,7 +362,12 @@ export const GraphCanvas = memo<GraphCanvasProps>( }) // Helper function to draw a single edge path - const drawEdgePath = (edge: typeof edges[0], sourceNode: GraphNode, targetNode: GraphNode, edgeShouldDim: boolean) => { + const drawEdgePath = ( + edge: (typeof edges)[0], + sourceNode: GraphNode, + targetNode: GraphNode, + edgeShouldDim: boolean, + ) => { const sourceX = sourceNode.x * zoom + panX const sourceY = sourceNode.y * zoom + panY const targetX = targetNode.x * zoom + panX @@ -381,9 +388,7 @@ export const GraphCanvas = memo<GraphCanvasProps>( const dy = targetY - sourceY const distance = Math.sqrt(dx * dx + dy * dy) const controlOffset = - edge.edgeType === "doc-memory" - ? 15 - : Math.min(30, distance * 0.2) + edge.edgeType === "doc-memory" ? 15 : Math.min(30, distance * 0.2) ctx.beginPath() ctx.moveTo(sourceX, sourceY) @@ -398,7 +403,7 @@ export const GraphCanvas = memo<GraphCanvasProps>( } // Smooth edge opacity: interpolate between full and 0.05 (dimmed) - const edgeDimOpacity = 1 - (dimProgress.current * 0.95) + const edgeDimOpacity = 1 - dimProgress.current * 0.95 // BATCH 1: Draw all doc-memory edges together if (docMemoryEdges.length > 0) { @@ -417,7 +422,8 @@ export const GraphCanvas = memo<GraphCanvasProps>( : edge.target if (sourceNode && targetNode) { - const edgeShouldDim = selectedNodeId !== null && + const edgeShouldDim = + selectedNodeId !== null && sourceNode.id !== selectedNodeId && targetNode.id !== selectedNodeId const opacity = edgeShouldDim ? edgeDimOpacity : 0.9 @@ -444,10 +450,13 @@ export const GraphCanvas = memo<GraphCanvasProps>( : edge.target if (sourceNode && targetNode) { - const edgeShouldDim = selectedNodeId !== null && + const edgeShouldDim = + selectedNodeId !== null && sourceNode.id !== selectedNodeId && targetNode.id !== selectedNodeId - const opacity = edgeShouldDim ? edgeDimOpacity : Math.max(0, edge.similarity * 0.5) + const opacity = edgeShouldDim + ? edgeDimOpacity + : Math.max(0, edge.similarity * 0.5) const lineWidth = Math.max(1, edge.similarity * 2) // Set color based on similarity strength @@ -480,7 +489,8 @@ export const GraphCanvas = memo<GraphCanvasProps>( : edge.target if (sourceNode && targetNode) { - const edgeShouldDim = selectedNodeId !== null && + const edgeShouldDim = + selectedNodeId !== null && sourceNode.id !== selectedNodeId && targetNode.id !== selectedNodeId const opacity = edgeShouldDim ? edgeDimOpacity : 0.8 @@ -568,7 +578,7 @@ export const GraphCanvas = memo<GraphCanvasProps>( const isSelected = selectedNodeId === node.id const shouldDim = selectedNodeId !== null && !isSelected // Smooth opacity: interpolate between 1 (full) and 0.1 (dimmed) based on animation progress - const nodeOpacity = shouldDim ? 1 - (dimProgress.current * 0.9) : 1 + const nodeOpacity = shouldDim ? 1 - dimProgress.current * 0.9 : 1 const isHighlightedDocument = (() => { if (node.type !== "document" || highlightSet.size === 0) return false const doc = node.data as DocumentWithMemories @@ -710,7 +720,7 @@ export const GraphCanvas = memo<GraphCanvasProps>( const radius = nodeSize / 2 ctx.fillStyle = fillColor - ctx.globalAlpha = shouldDim ? nodeOpacity : (isLatest ? 1 : 0.4) + ctx.globalAlpha = shouldDim ? nodeOpacity : isLatest ? 1 : 0.4 ctx.strokeStyle = borderColor ctx.lineWidth = isDragging ? 3 : isHovered ? 2 : 1.5 @@ -833,7 +843,17 @@ export const GraphCanvas = memo<GraphCanvasProps>( }) ctx.globalAlpha = 1 - }, [nodes, edges, panX, panY, zoom, width, height, highlightDocumentIds, nodeMap]) + }, [ + nodes, + edges, + panX, + panY, + zoom, + width, + height, + highlightDocumentIds, + nodeMap, + ]) // Hybrid rendering: continuous when simulation active, change-based when idle const lastRenderParams = useRef<number>(0) @@ -857,9 +877,16 @@ export const GraphCanvas = memo<GraphCanvasProps>( }, 0) // Combine all factors into a single number - return positionHash ^ edges.length ^ - Math.round(panX) ^ Math.round(panY) ^ - Math.round(zoom * 100) ^ width ^ height ^ highlightHash + return ( + positionHash ^ + edges.length ^ + Math.round(panX) ^ + Math.round(panY) ^ + Math.round(zoom * 100) ^ + width ^ + height ^ + highlightHash + ) }, [ nodes, edges.length, @@ -962,13 +989,10 @@ export const GraphCanvas = memo<GraphCanvasProps>( // Calculate effective DPR that keeps us within safe limits // Prevent division by zero by checking for valid dimensions - const maxDpr = width > 0 && height > 0 - ? Math.min( - MAX_CANVAS_SIZE / width, - MAX_CANVAS_SIZE / height, - dpr - ) - : dpr + const maxDpr = + width > 0 && height > 0 + ? Math.min(MAX_CANVAS_SIZE / width, MAX_CANVAS_SIZE / height, dpr) + : dpr // upscale backing store with clamped dimensions canvas.style.width = `${width}px` @@ -1026,4 +1050,4 @@ export const GraphCanvas = memo<GraphCanvasProps>( }, ) -GraphCanvas.displayName = "GraphCanvas"
\ No newline at end of file +GraphCanvas.displayName = "GraphCanvas" diff --git a/packages/memory-graph/src/components/memory-graph.tsx b/packages/memory-graph/src/components/memory-graph.tsx index b8dd493d..3368bc4d 100644 --- a/packages/memory-graph/src/components/memory-graph.tsx +++ b/packages/memory-graph/src/components/memory-graph.tsx @@ -2,7 +2,14 @@ import { GlassMenuEffect } from "@/ui/glass-effect" import { AnimatePresence } from "motion/react" -import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react" +import { + useCallback, + useEffect, + useMemo, + useReducer, + useRef, + useState, +} from "react" import { GraphCanvas } from "./graph-canvas" import { useGraphData } from "@/hooks/use-graph-data" import { useGraphInteractions } from "@/hooks/use-graph-interactions" @@ -426,8 +433,10 @@ export const MemoryGraph = ({ // Calculate node dimensions to position popover with proper gap const nodeSize = selectedNodeData.size * zoom - const nodeWidth = selectedNodeData.type === "document" ? nodeSize * 1.4 : nodeSize - const nodeHeight = selectedNodeData.type === "document" ? nodeSize * 0.9 : nodeSize + const nodeWidth = + selectedNodeData.type === "document" ? nodeSize * 1.4 : nodeSize + const nodeHeight = + selectedNodeData.type === "document" ? nodeSize * 0.9 : nodeSize const gap = 20 // Gap between node and popover // Smart positioning: flip to other side if would go off-screen @@ -457,7 +466,14 @@ export const MemoryGraph = ({ } return { x: popoverX, y: popoverY } - }, [selectedNodeData, zoom, panX, panY, containerSize.width, containerSize.height]) + }, [ + selectedNodeData, + zoom, + panX, + panY, + containerSize.width, + containerSize.height, + ]) // Viewport-based loading: load more when most documents are visible (optional) const checkAndLoadMore = useCallback(() => { @@ -564,7 +580,14 @@ export const MemoryGraph = ({ containerSizeRef.current = containerSize onSlideshowNodeChangeRef.current = onSlideshowNodeChange forceSimulationRef.current = forceSimulation - }, [nodes, handleNodeClick, centerViewportOn, containerSize, onSlideshowNodeChange, forceSimulation]) + }, [ + nodes, + handleNodeClick, + centerViewportOn, + containerSize, + onSlideshowNodeChange, + forceSimulation, + ]) useEffect(() => { // Clear any existing interval and timeout when isSlideshowActive changes @@ -731,7 +754,7 @@ export const MemoryGraph = ({ height={containerSize.height} nodes={nodes} highlightDocumentIds={highlightsVisible ? highlightDocumentIds : []} - isSimulationActive={forceSimulation.isActive()} + isSimulationActive={forceSimulation.isActive()} onDoubleClick={handleDoubleClick} onNodeClick={handleNodeClickWithPhysics} onNodeDragEnd={handleNodeDragEndWithPhysics} diff --git a/packages/memory-graph/src/components/navigation-controls.css.ts b/packages/memory-graph/src/components/navigation-controls.css.ts index c17f09b4..318cf067 100644 --- a/packages/memory-graph/src/components/navigation-controls.css.ts +++ b/packages/memory-graph/src/components/navigation-controls.css.ts @@ -17,7 +17,7 @@ const navButtonBase = style({ backgroundColor: "rgba(0, 0, 0, 0.2)", backdropFilter: "blur(8px)", WebkitBackdropFilter: "blur(8px)", - border: `1px solid rgba(255, 255, 255, 0.1)`, + border: "1px solid rgba(255, 255, 255, 0.1)", borderRadius: themeContract.radii.lg, padding: themeContract.space[2], color: "rgba(255, 255, 255, 0.7)", diff --git a/packages/memory-graph/src/components/node-detail-panel.tsx b/packages/memory-graph/src/components/node-detail-panel.tsx index b022364d..578224b2 100644 --- a/packages/memory-graph/src/components/node-detail-panel.tsx +++ b/packages/memory-graph/src/components/node-detail-panel.tsx @@ -68,7 +68,7 @@ const getDocumentIcon = (type: string) => { return <PDF {...iconProps} /> default: { - /*@ts-ignore */ + /*@ts-expect-error */ } return <FileText {...iconProps} /> } @@ -109,7 +109,7 @@ export const NodeDetailPanel = memo(function NodeDetailPanel({ {isDocument ? ( getDocumentIcon((data as DocumentWithMemories).type ?? "") ) : ( - // @ts-ignore + // @ts-expect-error <Brain className={styles.headerIconMemory} /> )} <HeadingH3Bold>{isDocument ? "Document" : "Memory"}</HeadingH3Bold> diff --git a/packages/memory-graph/src/components/node-popover.tsx b/packages/memory-graph/src/components/node-popover.tsx index 8c798110..5570038d 100644 --- a/packages/memory-graph/src/components/node-popover.tsx +++ b/packages/memory-graph/src/components/node-popover.tsx @@ -55,7 +55,11 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ return ( <> {/* Invisible backdrop to catch clicks outside */} - <div onClick={handleBackdropClick} className={backdropClassName} style={backdropStyle} /> + <div + onClick={handleBackdropClick} + className={backdropClassName} + style={backdropStyle} + /> {/* Popover content */} <div @@ -72,16 +76,24 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ {/* Header */} <div className={styles.header}> <div className={styles.headerTitle}> - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={styles.headerIcon}> - <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path> - <polyline points="14 2 14 8 20 8"></polyline> - <line x1="16" y1="13" x2="8" y2="13"></line> - <line x1="16" y1="17" x2="8" y2="17"></line> - <polyline points="10 9 9 9 8 9"></polyline> + <svg + width="20" + height="20" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + className={styles.headerIcon} + > + <path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" /> + <polyline points="14 2 14 8 20 8" /> + <line x1="16" y1="13" x2="8" y2="13" /> + <line x1="16" y1="17" x2="8" y2="17" /> + <polyline points="10 9 9 9 8 9" /> </svg> - <h3 className={styles.title}> - Document - </h3> + <h3 className={styles.title}>Document</h3> </div> <button type="button" @@ -96,9 +108,7 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ <div className={styles.sectionsContainer}> {/* Title */} <div> - <div className={styles.fieldLabel}> - Title - </div> + <div className={styles.fieldLabel}>Title</div> <p className={styles.fieldValue}> {(node.data as any).title || "Untitled Document"} </p> @@ -107,9 +117,7 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ {/* Summary - truncated to 2 lines */} {(node.data as any).summary && ( <div> - <div className={styles.fieldLabel}> - Summary - </div> + <div className={styles.fieldLabel}>Summary</div> <p className={styles.summaryValue}> {(node.data as any).summary} </p> @@ -118,9 +126,7 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ {/* Type */} <div> - <div className={styles.fieldLabel}> - Type - </div> + <div className={styles.fieldLabel}>Type</div> <p className={styles.fieldValue}> {(node.data as any).type || "Document"} </p> @@ -128,9 +134,7 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ {/* Memory Count */} <div> - <div className={styles.fieldLabel}> - Memory Count - </div> + <div className={styles.fieldLabel}>Memory Count</div> <p className={styles.fieldValue}> {(node.data as any).memoryEntries?.length || 0} memories </p> @@ -139,9 +143,7 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ {/* URL */} {((node.data as any).url || (node.data as any).customId) && ( <div> - <div className={styles.fieldLabel}> - URL - </div> + <div className={styles.fieldLabel}>URL</div> <a href={(() => { const doc = node.data as any @@ -160,10 +162,19 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ rel="noopener noreferrer" className={styles.link} > - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> - <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path> - <polyline points="15 3 21 3 21 9"></polyline> - <line x1="10" y1="14" x2="21" y2="3"></line> + <svg + width="12" + height="12" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + > + <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> + <polyline points="15 3 21 3 21 9" /> + <line x1="10" y1="14" x2="21" y2="3" /> </svg> View Document </a> @@ -173,20 +184,42 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ {/* Footer with metadata */} <div className={styles.footer}> <div className={styles.footerItem}> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> - <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect> - <line x1="16" y1="2" x2="16" y2="6"></line> - <line x1="8" y1="2" x2="8" y2="6"></line> - <line x1="3" y1="10" x2="21" y2="10"></line> + <svg + width="12" + height="12" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + > + <rect x="3" y="4" width="18" height="18" rx="2" ry="2" /> + <line x1="16" y1="2" x2="16" y2="6" /> + <line x1="8" y1="2" x2="8" y2="6" /> + <line x1="3" y1="10" x2="21" y2="10" /> </svg> - <span>{new Date((node.data as any).createdAt).toLocaleDateString()}</span> + <span> + {new Date( + (node.data as any).createdAt, + ).toLocaleDateString()} + </span> </div> <div className={styles.footerItemId}> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> - <line x1="4" y1="9" x2="20" y2="9"></line> - <line x1="4" y1="15" x2="20" y2="15"></line> - <line x1="10" y1="3" x2="8" y2="21"></line> - <line x1="16" y1="3" x2="14" y2="21"></line> + <svg + width="12" + height="12" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + > + <line x1="4" y1="9" x2="20" y2="9" /> + <line x1="4" y1="15" x2="20" y2="15" /> + <line x1="10" y1="3" x2="8" y2="21" /> + <line x1="16" y1="3" x2="14" y2="21" /> </svg> <span className={styles.idText}>{node.id}</span> </div> @@ -199,13 +232,21 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ {/* Header */} <div className={styles.header}> <div className={styles.headerTitle}> - <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className={styles.headerIconMemory}> - <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z"></path> - <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z"></path> + <svg + width="20" + height="20" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + className={styles.headerIconMemory} + > + <path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96.44 2.5 2.5 0 0 1-2.96-3.08 3 3 0 0 1-.34-5.58 2.5 2.5 0 0 1 1.32-4.24 2.5 2.5 0 0 1 1.98-3A2.5 2.5 0 0 1 9.5 2Z" /> + <path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96.44 2.5 2.5 0 0 0 2.96-3.08 3 3 0 0 0 .34-5.58 2.5 2.5 0 0 0-1.32-4.24 2.5 2.5 0 0 0-1.98-3A2.5 2.5 0 0 0 14.5 2Z" /> </svg> - <h3 className={styles.title}> - Memory - </h3> + <h3 className={styles.title}>Memory</h3> </div> <button type="button" @@ -220,31 +261,31 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ <div className={styles.sectionsContainer}> {/* Memory content */} <div> - <div className={styles.fieldLabel}> - Memory - </div> + <div className={styles.fieldLabel}>Memory</div> <p className={styles.fieldValue}> - {(node.data as any).memory || (node.data as any).content || "No content"} + {(node.data as any).memory || + (node.data as any).content || + "No content"} </p> {(node.data as any).isForgotten && ( - <div className={styles.forgottenBadge}> - Forgotten - </div> + <div className={styles.forgottenBadge}>Forgotten</div> )} {/* Expires (inline with memory if exists) */} {(node.data as any).forgetAfter && ( <p className={styles.expiresText}> - Expires: {new Date((node.data as any).forgetAfter).toLocaleDateString()} - {(node.data as any).forgetReason && ` - ${(node.data as any).forgetReason}`} + Expires:{" "} + {new Date( + (node.data as any).forgetAfter, + ).toLocaleDateString()} + {(node.data as any).forgetReason && + ` - ${(node.data as any).forgetReason}`} </p> )} </div> {/* Space */} <div> - <div className={styles.fieldLabel}> - Space - </div> + <div className={styles.fieldLabel}>Space</div> <p className={styles.fieldValue}> {(node.data as any).spaceId || "Default"} </p> @@ -253,20 +294,42 @@ export const NodePopover = memo<NodePopoverProps>(function NodePopover({ {/* Footer with metadata */} <div className={styles.footer}> <div className={styles.footerItem}> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> - <rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect> - <line x1="16" y1="2" x2="16" y2="6"></line> - <line x1="8" y1="2" x2="8" y2="6"></line> - <line x1="3" y1="10" x2="21" y2="10"></line> + <svg + width="12" + height="12" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + > + <rect x="3" y="4" width="18" height="18" rx="2" ry="2" /> + <line x1="16" y1="2" x2="16" y2="6" /> + <line x1="8" y1="2" x2="8" y2="6" /> + <line x1="3" y1="10" x2="21" y2="10" /> </svg> - <span>{new Date((node.data as any).createdAt).toLocaleDateString()}</span> + <span> + {new Date( + (node.data as any).createdAt, + ).toLocaleDateString()} + </span> </div> <div className={styles.footerItemId}> - <svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> - <line x1="4" y1="9" x2="20" y2="9"></line> - <line x1="4" y1="15" x2="20" y2="15"></line> - <line x1="10" y1="3" x2="8" y2="21"></line> - <line x1="16" y1="3" x2="14" y2="21"></line> + <svg + width="12" + height="12" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" + > + <line x1="4" y1="9" x2="20" y2="9" /> + <line x1="4" y1="15" x2="20" y2="15" /> + <line x1="10" y1="3" x2="8" y2="21" /> + <line x1="16" y1="3" x2="14" y2="21" /> </svg> <span className={styles.idText}>{node.id}</span> </div> diff --git a/packages/memory-graph/src/hooks/use-force-simulation.ts b/packages/memory-graph/src/hooks/use-force-simulation.ts index d409a4b1..32c38962 100644 --- a/packages/memory-graph/src/hooks/use-force-simulation.ts +++ b/packages/memory-graph/src/hooks/use-force-simulation.ts @@ -127,9 +127,8 @@ export function useForceSimulation( // Update edges if (edges.length > 0) { - const linkForce = simulationRef.current.force< - d3.ForceLink<GraphNode, GraphEdge> - >("link") + const linkForce = + simulationRef.current.force<d3.ForceLink<GraphNode, GraphEdge>>("link") if (linkForce) { linkForce.links(edges) } diff --git a/packages/memory-graph/src/hooks/use-graph-data.ts b/packages/memory-graph/src/hooks/use-graph-data.ts index 9bcd0d55..a94bebdc 100644 --- a/packages/memory-graph/src/hooks/use-graph-data.ts +++ b/packages/memory-graph/src/hooks/use-graph-data.ts @@ -19,7 +19,16 @@ import type { export function useGraphData( data: DocumentsResponse | null, selectedSpace: string, - nodePositions: Map<string, { x: number; y: number; parentDocId?: string; offsetX?: number; offsetY?: number }>, + nodePositions: Map< + string, + { + x: number + y: number + parentDocId?: string + offsetX?: number + offsetY?: number + } + >, draggingNodeId: string | null, memoryLimit?: number, maxNodes?: number, @@ -60,35 +69,34 @@ export function useGraphData( }) // Filter by space and prepare documents - let processedDocs = sortedDocs - .map((doc) => { - let memories = - selectedSpace === "all" - ? doc.memoryEntries - : doc.memoryEntries.filter( - (memory) => - (memory.spaceContainerTag ?? memory.spaceId ?? "default") === - selectedSpace, - ) - - // Sort memories by relevance score (if available) or recency - memories = memories.sort((a, b) => { - // Prioritize sourceRelevanceScore if available - if (a.sourceRelevanceScore != null && b.sourceRelevanceScore != null) { - return b.sourceRelevanceScore - a.sourceRelevanceScore // Higher score first - } - // Fall back to most recent - const dateA = new Date(a.updatedAt || a.createdAt).getTime() - const dateB = new Date(b.updatedAt || b.createdAt).getTime() - return dateB - dateA // Most recent first - }) - - return { - ...doc, - memoryEntries: memories, + let processedDocs = sortedDocs.map((doc) => { + let memories = + selectedSpace === "all" + ? doc.memoryEntries + : doc.memoryEntries.filter( + (memory) => + (memory.spaceContainerTag ?? memory.spaceId ?? "default") === + selectedSpace, + ) + + // Sort memories by relevance score (if available) or recency + memories = memories.sort((a, b) => { + // Prioritize sourceRelevanceScore if available + if (a.sourceRelevanceScore != null && b.sourceRelevanceScore != null) { + return b.sourceRelevanceScore - a.sourceRelevanceScore // Higher score first } + // Fall back to most recent + const dateA = new Date(a.updatedAt || a.createdAt).getTime() + const dateB = new Date(b.updatedAt || b.createdAt).getTime() + return dateB - dateA // Most recent first }) + return { + ...doc, + memoryEntries: memories, + } + }) + // Apply maxNodes limit using Option B (dynamic cap per document) if (maxNodes && maxNodes > 0) { const totalDocs = processedDocs.length @@ -112,37 +120,57 @@ export function useGraphData( // If we still have budget left, distribute remaining nodes to first docs let remainingBudget = maxNodes - totalNodes if (remainingBudget > 0) { - for (let i = 0; i < processedDocs.length && remainingBudget > 0; i++) { + for ( + let i = 0; + i < processedDocs.length && remainingBudget > 0; + i++ + ) { const doc = processedDocs[i] if (!doc) continue - const originalDoc = sortedDocs.find(d => d.id === doc.id) + const originalDoc = sortedDocs.find((d) => d.id === doc.id) if (!originalDoc) continue const currentMemCount = doc.memoryEntries.length const originalMemCount = originalDoc.memoryEntries.filter( - m => selectedSpace === "all" || - (m.spaceContainerTag ?? m.spaceId ?? "default") === selectedSpace + (m) => + selectedSpace === "all" || + (m.spaceContainerTag ?? m.spaceId ?? "default") === + selectedSpace, ).length // Can we add more memories to this doc? const canAdd = originalMemCount - currentMemCount if (canAdd > 0) { const toAdd = Math.min(canAdd, remainingBudget) - const additionalMems = doc.memoryEntries.slice(0, currentMemCount + toAdd) + const additionalMems = doc.memoryEntries.slice( + 0, + currentMemCount + toAdd, + ) processedDocs[i] = { ...doc, memoryEntries: originalDoc.memoryEntries - .filter(m => selectedSpace === "all" || - (m.spaceContainerTag ?? m.spaceId ?? "default") === selectedSpace) + .filter( + (m) => + selectedSpace === "all" || + (m.spaceContainerTag ?? m.spaceId ?? "default") === + selectedSpace, + ) .sort((a, b) => { - if (a.sourceRelevanceScore != null && b.sourceRelevanceScore != null) { + if ( + a.sourceRelevanceScore != null && + b.sourceRelevanceScore != null + ) { return b.sourceRelevanceScore - a.sourceRelevanceScore } - const dateA = new Date(a.updatedAt || a.createdAt).getTime() - const dateB = new Date(b.updatedAt || b.createdAt).getTime() + const dateA = new Date( + a.updatedAt || a.createdAt, + ).getTime() + const dateB = new Date( + b.updatedAt || b.createdAt, + ).getTime() return dateB - dateA }) - .slice(0, currentMemCount + toAdd) + .slice(0, currentMemCount + toAdd), } remainingBudget -= toAdd } @@ -238,8 +266,7 @@ export function useGraphData( }) // Enhanced Layout with Space Separation - const { centerX, centerY, clusterRadius } = - LAYOUT_CONSTANTS + const { centerX, centerY, clusterRadius } = LAYOUT_CONSTANTS /* 1. Build DOCUMENT nodes with space-aware clustering */ const documentNodes: GraphNode[] = [] @@ -255,8 +282,10 @@ export function useGraphData( // Loose grid spacing - physics will organize it better const spacing = 200 - const defaultX = centerX + (col - gridSize / 2) * spacing + (Math.random() - 0.5) * 50 - const defaultY = centerY + (row - gridSize / 2) * spacing + (Math.random() - 0.5) * 50 + const defaultX = + centerX + (col - gridSize / 2) * spacing + (Math.random() - 0.5) * 50 + const defaultY = + centerY + (row - gridSize / 2) * spacing + (Math.random() - 0.5) * 50 const customPos = nodePositions.get(doc.id) @@ -294,7 +323,7 @@ export function useGraphData( // D3-force will handle collision avoidance and spacing dynamically allNodes.push(...documentNodes) - + /* 3. Add memories around documents WITH doc-memory connections */ documentNodes.forEach((docNode) => { const memoryNodeMap = new Map<string, GraphNode>() @@ -318,9 +347,11 @@ export function useGraphData( if (customMemPos) { // If memory was manually positioned and has stored offset relative to parent - if (customMemPos.parentDocId === docNode.id && + if ( + customMemPos.parentDocId === docNode.id && customMemPos.offsetX !== undefined && - customMemPos.offsetY !== undefined) { + customMemPos.offsetY !== undefined + ) { // Apply the stored offset to the current document position finalMemX = docNode.x + customMemPos.offsetX finalMemY = docNode.y + customMemPos.offsetY @@ -386,7 +417,8 @@ export function useGraphData( 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> = (mem.memoryRelations ?? {}) as Record<string, MemoryRelation> + let parentRelations: Record<string, MemoryRelation> = + (mem.memoryRelations ?? {}) as Record<string, MemoryRelation> if ( mem.memoryRelations && @@ -436,4 +468,4 @@ export function useGraphData( return { nodes: allNodes, edges: allEdges } }, [data, filteredDocuments, nodePositions, draggingNodeId, similarityEdges]) -}
\ No newline at end of file +} diff --git a/packages/memory-graph/src/hooks/use-graph-interactions.ts b/packages/memory-graph/src/hooks/use-graph-interactions.ts index bcf0f5dd..d9044ad3 100644 --- a/packages/memory-graph/src/hooks/use-graph-interactions.ts +++ b/packages/memory-graph/src/hooks/use-graph-interactions.ts @@ -24,7 +24,16 @@ export function useGraphInteractions( nodeY: 0, }) const [nodePositions, setNodePositions] = useState< - Map<string, { x: number; y: number; parentDocId?: string; offsetX?: number; offsetY?: number }> + Map< + string, + { + x: number + y: number + parentDocId?: string + offsetX?: number + offsetY?: number + } + > >(new Map()) // Touch gesture state @@ -125,8 +134,11 @@ export function useGraphInteractions( // For memory nodes, find the parent document and store relative offset const memoryData = draggedNode.data as any // MemoryEntry type const parentDoc = nodes?.find( - (n) => n.type === "document" && - (n.data as any).memoryEntries?.some((m: any) => m.id === memoryData.id) + (n) => + n.type === "document" && + (n.data as any).memoryEntries?.some( + (m: any) => m.id === memoryData.id, + ), ) if (parentDoc) { @@ -140,7 +152,7 @@ export function useGraphInteractions( y: newY, parentDocId: parentDoc.id, offsetX, - offsetY + offsetY, }), ) return |