mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-11-01 03:31:43 +00:00
397 lines
11 KiB
Text
397 lines
11 KiB
Text
---
|
|
title: 'Memory Graph'
|
|
sidebarTitle: "Memory Graph"
|
|
description: 'Interactive visualization for documents, memories and connections'
|
|
icon: "/icons/hugeicons/hierarchy-square-01.svg"
|
|
---
|
|
|
|
Memory Graph is a React component that visualizes your Supermemory documents and memories as an interactive network. Documents appear as rectangular nodes, memories as hexagonal nodes, and connections between them show relationships and similarity.
|
|
|
|
<Card title="@supermemory/memory-graph on npm" icon="/icons/hugeicons/package.svg" href="https://www.npmjs.com/package/@supermemory/memory-graph">
|
|
Check out the NPM page for more details
|
|
</Card>
|
|
|
|
## Installation
|
|
|
|
```bash
|
|
npm install @supermemory/memory-graph
|
|
```
|
|
|
|
**Requirements:** React 18.0.0 or higher
|
|
|
|
## Quick start
|
|
|
|
```tsx
|
|
'use client'; // For Next.js App Router
|
|
|
|
import { MemoryGraph } from '@supermemory/memory-graph';
|
|
import type { GraphApiDocument } from '@supermemory/memory-graph';
|
|
import { useEffect, useState } from 'react';
|
|
|
|
export default function GraphPage() {
|
|
const [documents, setDocuments] = useState<GraphApiDocument[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<Error | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetch('/api/graph')
|
|
.then(res => res.json())
|
|
.then(data => {
|
|
setDocuments(data.documents);
|
|
setIsLoading(false);
|
|
})
|
|
.catch(err => {
|
|
setError(err);
|
|
setIsLoading(false);
|
|
});
|
|
}, []);
|
|
|
|
return (
|
|
<div style={{ height: '100vh' }}>
|
|
<MemoryGraph
|
|
documents={documents}
|
|
isLoading={isLoading}
|
|
error={error}
|
|
variant="console"
|
|
/>
|
|
</div>
|
|
);
|
|
}
|
|
```
|
|
|
|
## Optional rendering and document navigation
|
|
|
|
Existing icon-based document nodes and connection styling remain the defaults. Opt in to stacked squares and stronger semantic connections with `appearance`:
|
|
|
|
```tsx
|
|
<MemoryGraph
|
|
documents={documents}
|
|
appearance={{ documentNodes: 'stacked', connections: 'emphasized' }}
|
|
openDocumentOnEnter
|
|
onOpenDocument={openPersistedDocument}
|
|
/>
|
|
```
|
|
|
|
`documentNodes` accepts `'icons'` or `'stacked'`; `connections` accepts `'default'` or `'emphasized'`. Both fields are optional and also work on `GraphCanvas`. Stacks stay inside existing node bounds and simplify at low zoom. Use `colors.edgeDocument` to configure document-source ink; older custom `GraphCanvas` palettes without that field use `textSecondary` in emphasized mode.
|
|
|
|
`openDocumentOnEnter` is off by default. With an `onOpenDocument` callback, `MemoryGraph` exposes a focused Enter action for the selected persisted document or one confirmed memory source. Typing, modifier shortcuts, repeats, composition and keys outside the graph do not open documents. Your callback retains responsibility for routing and access control; consumers without a callback receive no new opening behavior.
|
|
|
|
## Backend API route
|
|
|
|
Create an API route to fetch documents from Supermemory. The endpoint returns each document's memories as `memoryEntries` and its type as `type`; `MemoryGraph` reads `memories` and `documentType`, so map each document before returning it:
|
|
|
|
<CodeGroup>
|
|
|
|
```typescript Next.js App Router
|
|
// app/api/graph/route.ts
|
|
import { NextResponse } from 'next/server';
|
|
|
|
export async function GET() {
|
|
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
|
|
},
|
|
body: JSON.stringify({
|
|
page: 1,
|
|
limit: 500,
|
|
sort: 'createdAt',
|
|
order: 'desc',
|
|
}),
|
|
});
|
|
|
|
const data = await response.json();
|
|
return NextResponse.json({
|
|
...data,
|
|
documents: data.documents.map((doc: { type: string; memoryEntries: unknown[] }) => ({
|
|
...doc,
|
|
documentType: doc.type,
|
|
memories: doc.memoryEntries,
|
|
})),
|
|
});
|
|
}
|
|
```
|
|
|
|
```typescript Next.js Pages Router
|
|
// pages/api/graph.ts
|
|
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
|
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
|
|
},
|
|
body: JSON.stringify({ page: 1, limit: 500, sort: 'createdAt', order: 'desc' }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
res.json({
|
|
...data,
|
|
documents: data.documents.map((doc: { type: string; memoryEntries: unknown[] }) => ({
|
|
...doc,
|
|
documentType: doc.type,
|
|
memories: doc.memoryEntries,
|
|
})),
|
|
});
|
|
}
|
|
```
|
|
|
|
```javascript Express
|
|
app.get('/api/graph', async (req, res) => {
|
|
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Authorization': `Bearer ${process.env.SUPERMEMORY_API_KEY}`,
|
|
},
|
|
body: JSON.stringify({ page: 1, limit: 500, sort: 'createdAt', order: 'desc' }),
|
|
});
|
|
|
|
const data = await response.json();
|
|
res.json({
|
|
...data,
|
|
documents: data.documents.map((doc) => ({
|
|
...doc,
|
|
documentType: doc.type,
|
|
memories: doc.memoryEntries,
|
|
})),
|
|
});
|
|
});
|
|
```
|
|
|
|
</CodeGroup>
|
|
|
|
<Warning>
|
|
Never expose your Supermemory API key to the client. Always fetch data through your backend.
|
|
</Warning>
|
|
|
|
---
|
|
|
|
## Variants
|
|
|
|
**Console Variant** - Full dashboard view: themed surface with a dot grid, nodes colored by theme:
|
|
|
|
```tsx
|
|
<MemoryGraph documents={documents} variant="console" />
|
|
```
|
|
|
|
**Consumer Variant** - Embedded widget view: transparent surface, nodes colored by cluster:
|
|
|
|
```tsx
|
|
<MemoryGraph documents={documents} variant="consumer" />
|
|
```
|
|
|
|
---
|
|
|
|
## Examples
|
|
|
|
### With pagination
|
|
|
|
```tsx
|
|
'use client';
|
|
|
|
import { MemoryGraph } from '@supermemory/memory-graph';
|
|
import type { GraphApiDocument } from '@supermemory/memory-graph';
|
|
import { useCallback, useEffect, useState } from 'react';
|
|
|
|
export default function PaginatedGraph() {
|
|
const [documents, setDocuments] = useState<GraphApiDocument[]>([]);
|
|
const [page, setPage] = useState(1);
|
|
const [hasMore, setHasMore] = useState(true);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [isLoadingMore, setIsLoadingMore] = useState(false);
|
|
|
|
useEffect(() => { fetchPage(1, false); }, []);
|
|
|
|
const fetchPage = async (pageNum: number, append: boolean) => {
|
|
pageNum === 1 ? setIsLoading(true) : setIsLoadingMore(true);
|
|
|
|
const res = await fetch(`/api/graph?page=${pageNum}&limit=100`);
|
|
const data = await res.json();
|
|
|
|
append ? setDocuments(prev => [...prev, ...data.documents]) : setDocuments(data.documents);
|
|
setHasMore(data.pagination.currentPage < data.pagination.totalPages);
|
|
setIsLoading(false);
|
|
setIsLoadingMore(false);
|
|
};
|
|
|
|
const loadMore = useCallback(async () => {
|
|
if (!isLoadingMore && hasMore) {
|
|
const nextPage = page + 1;
|
|
setPage(nextPage);
|
|
await fetchPage(nextPage, true);
|
|
}
|
|
}, [page, hasMore, isLoadingMore]);
|
|
|
|
return (
|
|
<MemoryGraph
|
|
documents={documents}
|
|
isLoading={isLoading}
|
|
isLoadingMore={isLoadingMore}
|
|
hasMore={hasMore}
|
|
onLoadMore={loadMore}
|
|
/>
|
|
);
|
|
}
|
|
```
|
|
|
|
### Highlighting search results
|
|
|
|
```tsx
|
|
<MemoryGraph
|
|
documents={documents}
|
|
highlightDocumentIds={searchResults}
|
|
highlightsVisible={searchResults.length > 0}
|
|
/>
|
|
```
|
|
|
|
### Custom empty state
|
|
|
|
```tsx
|
|
<MemoryGraph documents={documents} isLoading={isLoading}>
|
|
<div style={{ textAlign: 'center', padding: '2rem' }}>
|
|
<h2>No memories yet</h2>
|
|
<p>Add content to see your knowledge graph</p>
|
|
</div>
|
|
</MemoryGraph>
|
|
```
|
|
|
|
---
|
|
|
|
## Props reference
|
|
|
|
### Core props
|
|
|
|
| Prop | Type | Default | Description |
|
|
|------|------|---------|-------------|
|
|
| `documents` | `GraphApiDocument[]` | `[]` | Array of documents to display |
|
|
| `isLoading` | `boolean` | `false` | Shows loading indicator |
|
|
| `error` | `Error \| null` | `null` | Error to display |
|
|
| `variant` | `"console" \| "consumer"` | `"console"` | Visual variant |
|
|
| `children` | `ReactNode` | - | Custom empty state content |
|
|
|
|
### Pagination props
|
|
|
|
| Prop | Type | Default | Description |
|
|
|------|------|---------|-------------|
|
|
| `isLoadingMore` | `boolean` | `false` | Shows indicator when loading more |
|
|
| `hasMore` | `boolean` | `false` | Whether more documents available |
|
|
| `onLoadMore` | `() => void` | - | Called to load more, for example when the user zooms out past the loaded nodes |
|
|
| `totalCount` | `number` | - | Total document count shown by the loading indicator |
|
|
|
|
### Display props
|
|
|
|
| Prop | Type | Default | Description |
|
|
|------|------|---------|-------------|
|
|
| `highlightDocumentIds` | `string[]` | `[]` | Document IDs to highlight |
|
|
| `highlightsVisible` | `boolean` | `true` | Whether highlights shown |
|
|
| `maxNodes` | `number` | - | Maximum number of nodes to display |
|
|
| `colors` | `Partial<GraphThemeColors>` | - | Theme color overrides |
|
|
| `labels` | `MemoryGraphLabels` | - | User-facing label overrides |
|
|
| `appearance` | `GraphAppearance` | - | Opt-in document node and connection styles (see above) |
|
|
| `openDocumentOnEnter` | `boolean` | `false` | Enable the Enter action that calls `onOpenDocument` |
|
|
| `onOpenDocument` | `(documentId: string) => void` | - | Called when the user opens a document |
|
|
|
|
---
|
|
|
|
## Data types
|
|
|
|
### GraphApiDocument
|
|
|
|
```typescript
|
|
interface GraphApiDocument {
|
|
id: string;
|
|
title: string | null;
|
|
summary: string | null;
|
|
documentType: string;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
memories: GraphApiMemory[];
|
|
}
|
|
```
|
|
|
|
### GraphApiMemory
|
|
|
|
```typescript
|
|
interface GraphApiMemory {
|
|
id: string;
|
|
memory: string;
|
|
content?: string | null;
|
|
isStatic: boolean;
|
|
spaceId: string;
|
|
isLatest: boolean;
|
|
isForgotten: boolean;
|
|
forgetAfter: string | null;
|
|
forgetReason: string | null;
|
|
version: number;
|
|
parentMemoryId: string | null;
|
|
rootMemoryId: string | null;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
relation?: 'updates' | 'extends' | 'derives' | null;
|
|
updatesMemoryId?: string | null;
|
|
nextVersionId?: string | null;
|
|
memoryRelations?: Record<string, 'updates' | 'extends' | 'derives'> | null;
|
|
spaceContainerTag?: string | null;
|
|
}
|
|
```
|
|
|
|
`DocumentWithMemories` and `MemoryEntry` are still exported as backward-compatible types.
|
|
|
|
---
|
|
|
|
## Exports
|
|
|
|
### Components
|
|
|
|
```typescript
|
|
import { MemoryGraph, GraphCanvas } from '@supermemory/memory-graph';
|
|
```
|
|
|
|
### Hooks
|
|
|
|
```typescript
|
|
import { useGraphData, useGraphTheme } from '@supermemory/memory-graph';
|
|
```
|
|
|
|
### Engine classes
|
|
|
|
```typescript
|
|
import {
|
|
ForceSimulation,
|
|
ViewportState,
|
|
SpatialIndex,
|
|
VersionChainIndex
|
|
} from '@supermemory/memory-graph';
|
|
```
|
|
|
|
### Constants
|
|
|
|
```typescript
|
|
import {
|
|
DEFAULT_COLORS,
|
|
DEFAULT_HOVER_POPOVER_Z_INDEX,
|
|
DEFAULT_LABELS,
|
|
FORCE_CONFIG,
|
|
GRAPH_SETTINGS
|
|
} from '@supermemory/memory-graph';
|
|
```
|
|
|
|
---
|
|
|
|
## Performance
|
|
|
|
The graph handles hundreds of nodes efficiently through:
|
|
- Canvas-based rendering (not DOM elements)
|
|
- Viewport culling (only draws visible nodes)
|
|
- Level-of-detail optimization (simplifies when zoomed out)
|
|
- Change-based rendering (only redraws when state changes)
|
|
|
|
For very large datasets (1000+ documents), use pagination to load data in chunks.
|
|
|
|
## Browser support
|
|
|
|
Works in all modern browsers supporting Canvas 2D API, ES2020, and CSS custom properties. Tested on Chrome, Firefox, Safari, and Edge.
|