diff --git a/apps/web/components/graph-layout-view.tsx b/apps/web/components/graph-layout-view.tsx index 449617171..b74eb4810 100644 --- a/apps/web/components/graph-layout-view.tsx +++ b/apps/web/components/graph-layout-view.tsx @@ -42,7 +42,7 @@ export const GraphLayoutView = memo(({ isChatOpen }) => { variant="consumer" highlightDocumentIds={allHighlightDocumentIds} highlightsVisible={isChatOpen} - maxNodes={200} + maxNodes={undefined} canvasRef={canvasRef} /> diff --git a/apps/web/components/memory-graph/hooks/use-graph-api.ts b/apps/web/components/memory-graph/hooks/use-graph-api.ts index 5710f6ca0..3c3dd5d00 100644 --- a/apps/web/components/memory-graph/hooks/use-graph-api.ts +++ b/apps/web/components/memory-graph/hooks/use-graph-api.ts @@ -126,10 +126,7 @@ export function useGraphApi(options: UseGraphApiOptions = {}) { }, getNextPageParam: (lastPage) => { const { currentPage, totalPages } = lastPage.pagination - if (currentPage < totalPages) { - return currentPage + 1 - } - return undefined + return currentPage < totalPages ? currentPage + 1 : undefined }, staleTime: 30 * 1000, enabled, diff --git a/apps/web/components/memory-graph/memory-graph-wrapper.tsx b/apps/web/components/memory-graph/memory-graph-wrapper.tsx index a387f58e0..655f2482e 100644 --- a/apps/web/components/memory-graph/memory-graph-wrapper.tsx +++ b/apps/web/components/memory-graph/memory-graph-wrapper.tsx @@ -27,7 +27,7 @@ export function MemoryGraph({ error: externalError = null, variant = "console", containerTags, - maxNodes = 200, + maxNodes, canvasRef, ...rest }: MemoryGraphWrapperProps) { @@ -59,7 +59,7 @@ export function MemoryGraph({ }) return ( -
+
{children} diff --git a/apps/web/package.json b/apps/web/package.json index 5551c607b..d021e3b1e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -44,7 +44,10 @@ "@react-router/fs-routes": "^7.6.2", "@react-router/node": "^7.6.2", "@react-router/serve": "^7.6.2", + "@repo/lib": "workspace:*", + "@repo/validation": "workspace:*", "@sentry/nextjs": "^10.33.0", + "@supermemory/memory-graph": "^0.2.0", "@tailwindcss/typography": "^0.5.16", "@tanstack/react-form": "^1.12.4", "@tanstack/react-query": "^5.90.14", @@ -104,10 +107,7 @@ "tw-animate-css": "^1.3.4", "use-debounce": "^10.1.0", "vaul": "^1.1.2", - "zustand": "^5.0.7", - "@repo/lib": "workspace:*", - "@repo/validation": "workspace:*", - "@supermemory/memory-graph": "workspace:*" + "zustand": "^5.0.7" }, "devDependencies": { "@biomejs/biome": "^2.2.2", diff --git a/bun.lock b/bun.lock index 39d9842ec..879532b4a 100644 --- a/bun.lock +++ b/bun.lock @@ -161,7 +161,7 @@ "@repo/lib": "workspace:*", "@repo/validation": "workspace:*", "@sentry/nextjs": "^10.33.0", - "@supermemory/memory-graph": "workspace:*", + "@supermemory/memory-graph": "^0.2.0", "@tailwindcss/typography": "^0.5.16", "@tanstack/react-form": "^1.12.4", "@tanstack/react-query": "^5.90.14", diff --git a/packages/memory-graph/src/components/memory-graph.tsx b/packages/memory-graph/src/components/memory-graph.tsx index 0602ce435..66631fd85 100644 --- a/packages/memory-graph/src/components/memory-graph.tsx +++ b/packages/memory-graph/src/components/memory-graph.tsx @@ -185,17 +185,56 @@ export function MemoryGraph({ // Drag end handled by InputHandler }, []) + // Load more when user zooms out enough that visible area dwarfs the node extent + const loadMoreTimerRef = useRef | null>(null) + const loadMoreRef = useRef({ hasMore, isLoadingMore, onLoadMore }) + loadMoreRef.current = { hasMore, isLoadingMore, onLoadMore } + const handleViewportChange = useCallback( (zoom: number, popoverVisible: boolean) => { setZoomDisplay(Math.round(zoom * 100)) - // Only increment viewportVersion (which triggers popover repositioning - // via activePopoverPosition useMemo) when a popover is actually visible. - // This avoids 60fps React reconciliation during plain panning/zooming. if (popoverVisible) { setViewportVersion((v) => v + 1) } + + const { hasMore: more, isLoadingMore: loading, onLoadMore: load } = loadMoreRef.current + if (!more || loading || !load || !viewportRef.current) return + + const vp = viewportRef.current + const currentNodes = nodes + if (currentNodes.length === 0) return + + const topLeft = vp.screenToWorld(0, 0) + const bottomRight = vp.screenToWorld(containerSize.width, containerSize.height) + const viewW = bottomRight.x - topLeft.x + const viewH = bottomRight.y - topLeft.y + + let minX = Infinity + let minY = Infinity + let maxX = -Infinity + let maxY = -Infinity + for (const n of currentNodes) { + if (n.x < minX) minX = n.x + if (n.y < minY) minY = n.y + if (n.x > maxX) maxX = n.x + if (n.y > maxY) maxY = n.y + } + + const nodeW = maxX - minX || 1 + const nodeH = maxY - minY || 1 + + // Only trigger when the visible area is 3x larger than the node extent + // (i.e. user has zoomed out significantly past the data) + const zoomedOut = viewW > nodeW * 3 || viewH > nodeH * 3 + + if (zoomedOut && !loadMoreTimerRef.current) { + loadMoreTimerRef.current = setTimeout(() => { + loadMoreTimerRef.current = null + loadMoreRef.current.onLoadMore?.() + }, 500) + } }, - [], + [nodes, containerSize.width, containerSize.height], ) // Navigation @@ -509,7 +548,7 @@ export function MemoryGraph({ display: "flex", alignItems: "center", justifyContent: "center", - backgroundColor: colors.bg, + backgroundColor: "transparent", borderRadius: 12, } @@ -535,9 +574,7 @@ export function MemoryGraph({ height: "100%", borderRadius: 12, overflow: "hidden", - backgroundColor: colors.bg, - backgroundImage: `radial-gradient(circle, ${colors.textMuted} 0.5px, transparent 0.5px)`, - backgroundSize: "16px 16px", + backgroundColor: "transparent", } const canvasContainerStyle: React.CSSProperties = { @@ -576,30 +613,6 @@ export function MemoryGraph({ colors={colors} /> - {!isLoading && hasMore && onLoadMore && ( - - )} {!isLoading && !nodes.some((n) => n.type === "document") && children && (
{children}