Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/web/components/graph-layout-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const GraphLayoutView = memo<GraphLayoutViewProps>(({ isChatOpen }) => {
variant="consumer"
highlightDocumentIds={allHighlightDocumentIds}
highlightsVisible={isChatOpen}
maxNodes={200}
maxNodes={undefined}
canvasRef={canvasRef}
/>
</div>
Expand Down
5 changes: 1 addition & 4 deletions apps/web/components/memory-graph/hooks/use-graph-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
8 changes: 6 additions & 2 deletions apps/web/components/memory-graph/memory-graph-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function MemoryGraph({
error: externalError = null,
variant = "console",
containerTags,
maxNodes = 200,
maxNodes,
canvasRef,
...rest
}: MemoryGraphWrapperProps) {
Expand Down Expand Up @@ -59,7 +59,7 @@ export function MemoryGraph({
})

return (
<div ref={containerRef} className="w-full h-full">
<div ref={containerRef} className="w-full h-full [&>div]:!bg-none">
<MemoryGraphBase
documents={documents}
isLoading={externalIsLoading || apiIsLoading}
Expand All @@ -71,6 +71,10 @@ export function MemoryGraph({
maxNodes={maxNodes}
canvasRef={canvasRef}
totalCount={totalCount}
colors={{
bg: "transparent",
edgeDerives: "#9ca3af",
} as any}
{...rest}
>
{children}
Expand Down
8 changes: 4 additions & 4 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

77 changes: 45 additions & 32 deletions packages/memory-graph/src/components/memory-graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReturnType<typeof setTimeout> | 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
Expand Down Expand Up @@ -509,7 +548,7 @@ export function MemoryGraph({
display: "flex",
alignItems: "center",
justifyContent: "center",
backgroundColor: colors.bg,
backgroundColor: "transparent",
borderRadius: 12,
}

Expand All @@ -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 = {
Expand Down Expand Up @@ -576,30 +613,6 @@ export function MemoryGraph({
colors={colors}
/>

{!isLoading && hasMore && onLoadMore && (
<button
type="button"
onClick={onLoadMore}
style={{
position: "absolute",
top: 16,
right: 16,
zIndex: 30,
borderRadius: 12,
border: `1px solid ${colors.controlBorder}`,
backgroundColor: colors.controlBg,
color: colors.textSecondary,
paddingLeft: 16,
paddingRight: 16,
paddingTop: 8,
paddingBottom: 8,
fontSize: 13,
cursor: "pointer",
}}
>
{isLoadingMore ? "Loading..." : "Load more"}
</button>
)}

{!isLoading && !nodes.some((n) => n.type === "document") && children && (
<div style={emptyStateStyle}>{children}</div>
Expand Down
Loading