This commit is contained in:
abhijeet sharma 2026-09-26 03:32:05 +05:30 • committed by GitHub
commit 4585d27081
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 359 additions and 90 deletions

View file

@ -25,11 +25,11 @@ npm install @supermemory/memory-graph
'use client'; // For Next.js App Router
import { MemoryGraph } from '@supermemory/memory-graph';
import type { DocumentWithMemories } from '@supermemory/memory-graph';
import type { GraphApiDocument } from '@supermemory/memory-graph';
import { useEffect, useState } from 'react';
export default function GraphPage() {
const [documents, setDocuments] = useState<DocumentWithMemories[]>([]);
const [documents, setDocuments] = useState<GraphApiDocument[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
@ -59,15 +59,97 @@ export default function GraphPage() {
}
```
<Note>
`documents` must be `GraphApiDocument[]`, the shape the API route below returns, with `documentType` and `memories` fields (not the raw API's `type` and `memoryEntries`).
</Note>
## Backend API Route
Create an API route to fetch documents from Supermemory:
Create an API route to fetch documents from Supermemory. The `/v3/documents/documents` response uses `type` and `memoryEntries`; `<MemoryGraph>` expects `documentType` and `memories`, so the route normalizes the shape before returning it:
<CodeGroup>
```typescript Next.js App Router
// app/api/graph/route.ts
import { NextResponse } from 'next/server';
import type { GraphApiDocument, GraphApiMemory } from '@supermemory/memory-graph';
interface RawMemoryEntry {
id: string;
memory?: string | null;
content?: string | null;
spaceId?: string | null;
isStatic?: boolean;
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;
}
interface RawDocument {
id: string;
title: string | null;
summary?: string | null;
type: string;
createdAt: string;
updatedAt: string;
memoryEntries: RawMemoryEntry[];
}
interface RawDocumentsResponse {
documents: RawDocument[];
pagination: {
currentPage: number;
limit: number;
totalItems: number;
totalPages: number;
};
}
// Maps the raw API document (`type` / `memoryEntries`) onto the shape
// <MemoryGraph> expects (`documentType` / `memories`), filling in the
// defaults for fields the API may omit.
function toGraphDocument(doc: RawDocument): GraphApiDocument {
return {
id: doc.id,
title: doc.title,
summary: doc.summary ?? null,
documentType: doc.type,
createdAt: doc.createdAt,
updatedAt: doc.updatedAt,
memories: doc.memoryEntries.map((mem): GraphApiMemory => ({
id: mem.id,
memory: mem.memory ?? mem.content ?? '',
content: mem.content ?? null,
isStatic: mem.isStatic ?? false,
spaceId: mem.spaceId ?? '',
isLatest: mem.isLatest ?? true,
isForgotten: mem.isForgotten ?? false,
forgetAfter: mem.forgetAfter ?? null,
forgetReason: mem.forgetReason ?? null,
version: mem.version ?? 1,
parentMemoryId: mem.parentMemoryId ?? null,
rootMemoryId: mem.rootMemoryId ?? null,
createdAt: mem.createdAt,
updatedAt: mem.updatedAt,
relation: mem.relation ?? null,
updatesMemoryId: mem.updatesMemoryId ?? null,
nextVersionId: mem.nextVersionId ?? null,
memoryRelations: mem.memoryRelations ?? null,
spaceContainerTag: mem.spaceContainerTag ?? null,
})),
};
}
export async function GET() {
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
@ -84,14 +166,95 @@ export async function GET() {
}),
});
const data = await response.json();
return NextResponse.json(data);
const data: RawDocumentsResponse = await response.json();
return NextResponse.json({
documents: data.documents.map(toGraphDocument),
pagination: data.pagination,
});
}
```
```typescript Next.js Pages Router
// pages/api/graph.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import type { GraphApiDocument, GraphApiMemory } from '@supermemory/memory-graph';
interface RawMemoryEntry {
id: string;
memory?: string | null;
content?: string | null;
spaceId?: string | null;
isStatic?: boolean;
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;
}
interface RawDocument {
id: string;
title: string | null;
summary?: string | null;
type: string;
createdAt: string;
updatedAt: string;
memoryEntries: RawMemoryEntry[];
}
interface RawDocumentsResponse {
documents: RawDocument[];
pagination: {
currentPage: number;
limit: number;
totalItems: number;
totalPages: number;
};
}
// Maps the raw API document (`type` / `memoryEntries`) onto the shape
// <MemoryGraph> expects (`documentType` / `memories`), filling in the
// defaults for fields the API may omit.
function toGraphDocument(doc: RawDocument): GraphApiDocument {
return {
id: doc.id,
title: doc.title,
summary: doc.summary ?? null,
documentType: doc.type,
createdAt: doc.createdAt,
updatedAt: doc.updatedAt,
memories: doc.memoryEntries.map((mem): GraphApiMemory => ({
id: mem.id,
memory: mem.memory ?? mem.content ?? '',
content: mem.content ?? null,
isStatic: mem.isStatic ?? false,
spaceId: mem.spaceId ?? '',
isLatest: mem.isLatest ?? true,
isForgotten: mem.isForgotten ?? false,
forgetAfter: mem.forgetAfter ?? null,
forgetReason: mem.forgetReason ?? null,
version: mem.version ?? 1,
parentMemoryId: mem.parentMemoryId ?? null,
rootMemoryId: mem.rootMemoryId ?? null,
createdAt: mem.createdAt,
updatedAt: mem.updatedAt,
relation: mem.relation ?? null,
updatesMemoryId: mem.updatesMemoryId ?? null,
nextVersionId: mem.nextVersionId ?? null,
memoryRelations: mem.memoryRelations ?? null,
spaceContainerTag: mem.spaceContainerTag ?? null,
})),
};
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const response = await fetch('https://api.supermemory.ai/v3/documents/documents', {
@ -103,8 +266,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
body: JSON.stringify({ page: 1, limit: 500, sort: 'createdAt', order: 'desc' }),
});
const data = await response.json();
res.json(data);
const data: RawDocumentsResponse = await response.json();
res.json({
documents: data.documents.map(toGraphDocument),
pagination: data.pagination,
});
}
```
@ -120,7 +286,41 @@ app.get('/api/graph', async (req, res) => {
});
const data = await response.json();
res.json(data);
// Map the raw API document (`type` / `memoryEntries`) onto the shape
// <MemoryGraph> expects (`documentType` / `memories`), filling in the
// defaults for fields the API may omit.
const documents = data.documents.map((doc) => ({
id: doc.id,
title: doc.title,
summary: doc.summary ?? null,
documentType: doc.type,
createdAt: doc.createdAt,
updatedAt: doc.updatedAt,
memories: doc.memoryEntries.map((mem) => ({
id: mem.id,
memory: mem.memory ?? mem.content ?? '',
content: mem.content ?? null,
isStatic: mem.isStatic ?? false,
spaceId: mem.spaceId ?? '',
isLatest: mem.isLatest ?? true,
isForgotten: mem.isForgotten ?? false,
forgetAfter: mem.forgetAfter ?? null,
forgetReason: mem.forgetReason ?? null,
version: mem.version ?? 1,
parentMemoryId: mem.parentMemoryId ?? null,
rootMemoryId: mem.rootMemoryId ?? null,
createdAt: mem.createdAt,
updatedAt: mem.updatedAt,
relation: mem.relation ?? null,
updatesMemoryId: mem.updatesMemoryId ?? null,
nextVersionId: mem.nextVersionId ?? null,
memoryRelations: mem.memoryRelations ?? null,
spaceContainerTag: mem.spaceContainerTag ?? null,
})),
}));
res.json({ documents, pagination: data.pagination });
});
```
@ -134,13 +334,13 @@ app.get('/api/graph', async (req, res) => {
## Variants
**Console Variant** - Full-featured dashboard view (0.8x zoom, space selector visible):
**Console Variant** - Full-featured dashboard view (0.8x initial zoom):
```tsx
<MemoryGraph documents={documents} variant="console" />
```
**Consumer Variant** - Embedded widget view (0.5x zoom, space selector hidden):
**Consumer Variant** - Embedded widget view (0.5x initial zoom):
```tsx
<MemoryGraph documents={documents} variant="consumer" />
@ -156,10 +356,11 @@ app.get('/api/graph', async (req, res) => {
'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([]);
const [documents, setDocuments] = useState<GraphApiDocument[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [isLoading, setIsLoading] = useState(true);
@ -193,8 +394,8 @@ export default function PaginatedGraph() {
isLoading={isLoading}
isLoadingMore={isLoadingMore}
hasMore={hasMore}
totalLoaded={documents.length}
loadMoreDocuments={loadMore}
totalCount={documents.length}
onLoadMore={loadMore}
/>
);
}
@ -210,17 +411,6 @@ export default function PaginatedGraph() {
/>
```
### Controlled Space Selection
```tsx
<MemoryGraph
documents={documents}
selectedSpace={selectedSpace}
onSpaceChange={setSelectedSpace}
showSpacesSelector={false}
/>
```
### Custom Empty State
```tsx
@ -240,80 +430,131 @@ export default function PaginatedGraph() {
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `documents` | `DocumentWithMemories[]` | required | Array of documents to display |
| `isLoading` | `boolean` | `false` | Shows loading indicator |
| `error` | `Error \| null` | `null` | Error to display |
| `documents` | `GraphApiDocument[]` | `[]` | Documents to display - pass this for direct data mode |
| `isLoading` | `boolean` | `false` | Whether data is loading |
| `error` | `Error \| null` | `null` | Error from data fetching |
| `variant` | `"console" \| "consumer"` | `"console"` | Visual variant |
| `children` | `ReactNode` | - | Custom empty state content |
| `children` | `ReactNode` | - | Rendered when there are no documents |
### Pagination Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isLoadingMore` | `boolean` | `false` | Shows indicator when loading more |
| `hasMore` | `boolean` | `false` | Whether more documents available |
| `totalLoaded` | `number` | - | Total documents currently loaded |
| `loadMoreDocuments` | `() => Promise<void>` | - | Callback to load more |
| `autoLoadOnViewport` | `boolean` | `true` | Auto-load when 80% visible |
| `isLoadingMore` | `boolean` | `false` | Whether more data is being loaded |
| `hasMore` | `boolean` | `false` | Whether there are more documents to load |
| `onLoadMore` | `() => void` | - | Callback to load more documents |
| `totalCount` | `number` | - | Total count, used by the loading indicator |
### Display Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `showSpacesSelector` | `boolean` | variant-based | Show space filter dropdown |
| `highlightDocumentIds` | `string[]` | `[]` | Document IDs to highlight |
| `highlightsVisible` | `boolean` | `true` | Whether highlights shown |
| `occludedRightPx` | `number` | `0` | Pixels occluded on right |
| `highlightDocumentIds` | `string[]` | - | Document IDs to highlight |
| `highlightsVisible` | `boolean` | - | Whether highlights are visible |
| `legendId` | `string` | - | Optional legend element id |
| `showFps` | `boolean` | - | Show FPS counter overlay |
| `canvasRef` | `RefObject<HTMLCanvasElement \| null>` | - | External ref to the canvas (e.g. for screenshot export) |
| `colors` | `Partial<GraphThemeColors>` | - | Custom theme colors, merged with CSS-variable/default values |
| `labels` | `MemoryGraphLabels` | - | Custom user-facing labels, merged with defaults |
| `layering` | `MemoryGraphLayering` | - | Overlay layering controls (e.g. hover popover z-index) |
### Controlled State Props
### Filtering Props
| Prop | Type | Description |
|------|------|-------------|
| `selectedSpace` | `string` | Currently selected space (use `"all"` for all) |
| `onSpaceChange` | `(spaceId: string) => void` | Callback when space changes |
| `memoryLimit` | `number` | Max memories per document when space selected |
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `containerTags` | `string[]` | - | Container tags for filtering (used by apps with their own API hooks) |
| `documentIds` | `string[]` | - | Specific document IDs to show |
| `maxNodes` | `number` | - | Max nodes to display |
### Slideshow Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `isSlideshowActive` | `boolean` | - | Whether slideshow mode is active |
| `onSlideshowNodeChange` | `(nodeId: string \| null) => void` | - | Called as the slideshow moves between nodes |
| `onSlideshowStop` | `() => void` | - | Called when the slideshow ends |
### Other
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `onOpenDocument` | `(documentId: string) => void` | - | Called when the user wants to view a document's full content |
---
## Data Types
### DocumentWithMemories
### GraphApiDocument / GraphApiMemory
This is the shape `documents` actually takes: `documentType` and `memories`, matching what the API route in [Backend API Route](#backend-api-route) normalizes into:
```typescript
interface GraphApiDocument {
id: string;
title: string | null;
summary: string | null;
documentType: string;
createdAt: string;
updatedAt: string;
memories: GraphApiMemory[];
}
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 / MemoryEntry
Also exported for backward compatibility with older API response shapes. Not what `documents` takes today, but still useful for typing a raw fetch response before mapping it to `GraphApiDocument`:
```typescript
interface DocumentWithMemories {
id: string;
customId?: string | null;
title?: string | null;
content?: string | null;
title: string | null;
url: string | null;
documentType: string;
createdAt: string;
updatedAt: string;
summary?: string | null;
url?: string | null;
source?: string | null;
type?: string | null;
status: 'pending' | 'processing' | 'done' | 'failed';
metadata?: Record<string, string | number | boolean> | null;
createdAt: string | Date;
updatedAt: string | Date;
memoryEntries: MemoryEntry[];
memories: MemoryEntry[];
}
```
### MemoryEntry
```typescript
interface MemoryEntry {
id: string;
documentId: string;
content: string | null;
summary?: string | null;
title?: string | null;
type?: string | null;
metadata?: Record<string, string | number | boolean> | null;
createdAt: string | Date;
updatedAt: string | Date;
spaceContainerTag?: string | null;
relation?: 'updates' | 'extends' | 'derives' | null;
isLatest?: boolean;
memory: string;
content?: string | null;
createdAt: string;
updatedAt: string;
spaceId?: string | null;
isStatic?: boolean;
isForgotten?: boolean;
forgetAfter?: string | null;
forgetReason?: string | null;
version?: number;
parentMemoryId?: string | null;
rootMemoryId?: string | null;
isLatest?: boolean;
relation?: 'updates' | 'extends' | 'derives' | null;
spaceContainerTag?: string | null;
}
```
@ -324,26 +565,52 @@ interface MemoryEntry {
### Components
```typescript
import {
MemoryGraph,
GraphCanvas,
Legend,
LoadingIndicator,
NodeDetailPanel,
SpacesDropdown
} from '@supermemory/memory-graph';
import { MemoryGraph, GraphCanvas } from '@supermemory/memory-graph';
```
### Hooks
```typescript
import { useGraphData, useGraphInteractions } from '@supermemory/memory-graph';
import { useGraphData, useGraphTheme } from '@supermemory/memory-graph';
```
### Engine Classes
For advanced usage: driving the force simulation, viewport, or hit-testing directly:
```typescript
import {
ForceSimulation,
ViewportState,
SpatialIndex,
VersionChainIndex,
} from '@supermemory/memory-graph';
```
### Constants
```typescript
import { colors, GRAPH_SETTINGS, LAYOUT_CONSTANTS } from '@supermemory/memory-graph';
import {
DEFAULT_COLORS,
DEFAULT_HOVER_POPOVER_Z_INDEX,
DEFAULT_LABELS,
FORCE_CONFIG,
GRAPH_SETTINGS,
} from '@supermemory/memory-graph';
```
### Mock Data
`@supermemory/memory-graph/mock-data` generates fake `GraphApiDocument[]` data for local development and stress testing, without a live API key:
```typescript
import { generateMockGraphData } from '@supermemory/memory-graph/mock-data';
const { documents } = generateMockGraphData({
documentCount: 100,
memoriesPerDoc: [2, 5],
seed: 12345,
});
```
---

View file

@ -14,10 +14,10 @@ Open [http://localhost:3000](http://localhost:3000) and enter your Supermemory A
## Usage Example
```tsx
import { MemoryGraph, type DocumentWithMemories } from '@supermemory/memory-graph'
import { MemoryGraph, type GraphApiDocument } from '@supermemory/memory-graph'
function App() {
const [documents, setDocuments] = useState<DocumentWithMemories[]>([])
const [documents, setDocuments] = useState<GraphApiDocument[]>([])
return (
<MemoryGraph
@ -26,10 +26,9 @@ function App() {
isLoadingMore={false}
error={null}
hasMore={false}
loadMoreDocuments={() => {}}
totalLoaded={documents.length}
onLoadMore={() => {}}
totalCount={documents.length}
variant="console"
showSpacesSelector={true}
>
<div>No memories found</div>
</MemoryGraph>
@ -37,16 +36,19 @@ function App() {
}
```
See [`src/app/page.tsx`](./src/app/page.tsx) for the full working example, including converting a raw API response into `GraphApiDocument[]`.
## Props
| Prop | Type | Description |
|------|------|-------------|
| `documents` | `DocumentWithMemories[]` | Array of documents to display |
| `documents` | `GraphApiDocument[]` | Array of documents to display |
| `isLoading` | `boolean` | Initial loading state |
| `isLoadingMore` | `boolean` | Loading more documents state |
| `error` | `Error \| null` | Error to display |
| `hasMore` | `boolean` | Whether more documents can be loaded |
| `loadMoreDocuments` | `() => void` | Callback to load more documents |
| `totalLoaded` | `number` | Total number of loaded documents |
| `variant` | `"default" \| "console"` | Visual theme variant |
| `showSpacesSelector` | `boolean` | Show space filter dropdown |
| `onLoadMore` | `() => void` | Callback to load more documents |
| `totalCount` | `number` | Total number of loaded documents |
| `variant` | `"console" \| "consumer"` | Visual variant |
See [`packages/memory-graph`](../../packages/memory-graph/src/types.ts) for the full `MemoryGraphProps` reference.