diff --git a/apps/docs/integrations/memory-graph.mdx b/apps/docs/integrations/memory-graph.mdx index decd5f52..34236a33 100644 --- a/apps/docs/integrations/memory-graph.mdx +++ b/apps/docs/integrations/memory-graph.mdx @@ -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([]); + const [documents, setDocuments] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); @@ -59,15 +59,97 @@ export default function GraphPage() { } ``` + + `documents` must be `GraphApiDocument[]`, the shape the API route below returns, with `documentType` and `memories` fields (not the raw API's `type` and `memoryEntries`). + + ## 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`; `` expects `documentType` and `memories`, so the route normalizes the shape before returning it: ```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 | 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 +// 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 | 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 +// 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 + // 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 ``` -**Consumer Variant** - Embedded widget view (0.5x zoom, space selector hidden): +**Consumer Variant** - Embedded widget view (0.5x initial zoom): ```tsx @@ -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([]); 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 - -``` - ### 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` | - | 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` | - | External ref to the canvas (e.g. for screenshot export) | +| `colors` | `Partial` | - | 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 | 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 | 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 | 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, +}); ``` --- diff --git a/apps/memory-graph-playground/README.md b/apps/memory-graph-playground/README.md index 5c143d9b..4c116ee4 100644 --- a/apps/memory-graph-playground/README.md +++ b/apps/memory-graph-playground/README.md @@ -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([]) + const [documents, setDocuments] = useState([]) return ( {}} - totalLoaded={documents.length} + onLoadMore={() => {}} + totalCount={documents.length} variant="console" - showSpacesSelector={true} >
No memories found
@@ -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.