From 14e688087db2fbfa1f2435f83a0d41c9e556e824 Mon Sep 17 00:00:00 2001 From: Mahesh Sanikommu Date: Mon, 18 May 2026 10:06:07 -0700 Subject: [PATCH] feat(graph): auto-paginate consumer graph + loading progress chip Accounts with more than 100 documents previously only saw the newest 100 in the memory graph until they manually zoomed out (and even then, only one extra page per zoom). For users with thousands of documents this made the graph appear nearly empty. Adds an opt-in `autoFetchAll` flag to `useGraphApi` that chains `fetchNextPage()` until every page is loaded, and renders a top-right progress chip ("X memories - Y/Z documents") with a spinner while pages stream in. The chip self-hides on completion. Only the consumer GraphLayoutView opts in; modal and chat-rail variants remain scoped to their existing single-page behaviour. This is a stopgap for high-memory users. A proper server-side sampling/clustering endpoint is needed to scale past tens of thousands of nodes and will be tracked separately. --- apps/web/components/graph-layout-view.tsx | 1 + .../memory-graph/hooks/use-graph-api.ts | 23 ++++++++++- .../memory-graph/memory-graph-wrapper.tsx | 38 ++++++++++++++++++- 3 files changed, 58 insertions(+), 4 deletions(-) diff --git a/apps/web/components/graph-layout-view.tsx b/apps/web/components/graph-layout-view.tsx index c1fc0311..0bbf6a3f 100644 --- a/apps/web/components/graph-layout-view.tsx +++ b/apps/web/components/graph-layout-view.tsx @@ -46,6 +46,7 @@ export const GraphLayoutView = memo(function GraphLayoutView({ maxNodes={undefined} canvasRef={canvasRef} onOpenDocument={onOpenDocument} + autoFetchAll /> 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 7991f600..41a7d63f 100644 --- a/apps/web/components/memory-graph/hooks/use-graph-api.ts +++ b/apps/web/components/memory-graph/hooks/use-graph-api.ts @@ -1,7 +1,7 @@ "use client" import { useInfiniteQuery } from "@tanstack/react-query" -import { useMemo } from "react" +import { useEffect, useMemo } from "react" import { $fetch } from "@lib/api" import type { GraphApiDocument, @@ -15,6 +15,7 @@ interface UseGraphApiOptions { containerTags?: string[] documentIds?: string[] enabled?: boolean + autoFetchAll?: boolean } interface ApiMemoryEntry { @@ -108,7 +109,12 @@ function toGraphDocument( } export function useGraphApi(options: UseGraphApiOptions = {}) { - const { containerTags, documentIds, enabled = true } = options + const { + containerTags, + documentIds, + enabled = true, + autoFetchAll = false, + } = options const filteredDocumentIds = documentIds?.filter(Boolean) const hasDocumentIds = filteredDocumentIds != null && filteredDocumentIds.length > 0 @@ -163,6 +169,19 @@ export function useGraphApi(options: UseGraphApiOptions = {}) { enabled, }) + useEffect(() => { + if (!autoFetchAll || !enabled) return + if (!hasNextPage || isPending || isFetchingNextPage) return + fetchNextPage() + }, [ + autoFetchAll, + enabled, + hasNextPage, + isPending, + isFetchingNextPage, + fetchNextPage, + ]) + const documents = useMemo(() => { if (!data?.pages) return [] return data.pages.flatMap((page) => diff --git a/apps/web/components/memory-graph/memory-graph-wrapper.tsx b/apps/web/components/memory-graph/memory-graph-wrapper.tsx index 0b2ca826..e2881387 100644 --- a/apps/web/components/memory-graph/memory-graph-wrapper.tsx +++ b/apps/web/components/memory-graph/memory-graph-wrapper.tsx @@ -1,9 +1,11 @@ "use client" -import { useEffect, useRef, useState } from "react" +import { useEffect, useMemo, useRef, useState } from "react" +import { Loader2 } from "lucide-react" import { MemoryGraph as MemoryGraphBase } from "@supermemory/memory-graph" import type { GraphThemeColors } from "@supermemory/memory-graph" import { useGraphApi } from "./hooks/use-graph-api" +import { cn } from "@lib/utils" export interface MemoryGraphWrapperProps { children?: React.ReactNode @@ -21,6 +23,7 @@ export interface MemoryGraphWrapperProps { onSlideshowStop?: () => void canvasRef?: React.RefObject onOpenDocument?: (documentId: string) => void + autoFetchAll?: boolean } export function MemoryGraph({ @@ -32,6 +35,7 @@ export function MemoryGraph({ documentIds, maxNodes, canvasRef, + autoFetchAll = false, ...rest }: MemoryGraphWrapperProps) { const [containerSize, setContainerSize] = useState({ width: 0, height: 0 }) @@ -60,10 +64,22 @@ export function MemoryGraph({ containerTags, documentIds, enabled: containerSize.width > 0 && containerSize.height > 0, + autoFetchAll, }) + const loadedDocCount = documents.length + const loadedMemoryCount = useMemo( + () => documents.reduce((sum, doc) => sum + doc.memories.length, 0), + [documents], + ) + const showProgress = + autoFetchAll && + !apiIsLoading && + totalCount > 0 && + (hasMore || isLoadingMore || loadedDocCount < totalCount) + return ( -
+
{children} + {showProgress && ( +
+ + + {loadedMemoryCount.toLocaleString()} memories ยท{" "} + {loadedDocCount.toLocaleString()}/{totalCount.toLocaleString()}{" "} + documents + +
+ )}
) }