mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-07 08:26:15 +00:00
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.
This commit is contained in:
parent
da47b1d7d3
commit
14e688087d
3 changed files with 58 additions and 4 deletions
|
|
@ -46,6 +46,7 @@ export const GraphLayoutView = memo(function GraphLayoutView({
|
|||
maxNodes={undefined}
|
||||
canvasRef={canvasRef}
|
||||
onOpenDocument={onOpenDocument}
|
||||
autoFetchAll
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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<HTMLCanvasElement | null>
|
||||
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 (
|
||||
<div ref={containerRef} className="size-full [&>div]:!bg-none">
|
||||
<div ref={containerRef} className="relative size-full [&>div]:!bg-none">
|
||||
<MemoryGraphBase
|
||||
documents={documents}
|
||||
isLoading={externalIsLoading || apiIsLoading}
|
||||
|
|
@ -85,6 +101,24 @@ export function MemoryGraph({
|
|||
>
|
||||
{children}
|
||||
</MemoryGraphBase>
|
||||
{showProgress && (
|
||||
<div
|
||||
className={cn(
|
||||
"absolute right-3 top-3 z-15 md:right-4 md:top-4",
|
||||
"flex items-center gap-2 rounded-full",
|
||||
"bg-background/85 px-3 py-1.5 text-xs backdrop-blur-md",
|
||||
"border border-border/60 shadow-sm",
|
||||
)}
|
||||
aria-live="polite"
|
||||
>
|
||||
<Loader2 className="size-3 shrink-0 animate-spin text-muted-foreground" />
|
||||
<span className="tabular-nums">
|
||||
{loadedMemoryCount.toLocaleString()} memories ·{" "}
|
||||
{loadedDocCount.toLocaleString()}/{totalCount.toLocaleString()}{" "}
|
||||
documents
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue