diff --git a/apps/mcp/src/client.ts b/apps/mcp/src/client.ts index 2924f6fb..179dd829 100644 --- a/apps/mcp/src/client.ts +++ b/apps/mcp/src/client.ts @@ -45,52 +45,41 @@ export interface Project { documentCount?: number } -// Graph API types -export interface GraphApiMemory { +// Documents API types +export interface DocumentMemoryEntry { id: string memory: string - isStatic: boolean - isLatest: boolean - isForgotten: boolean - forgetAfter: string | null - version: number - parentMemoryId: string | null + spaceId: string + isStatic?: boolean + isLatest?: boolean + isForgotten?: boolean + forgetAfter?: string | null + forgetReason?: string | null + version?: number + parentMemoryId?: string | null + rootMemoryId?: string | null createdAt: string updatedAt: string } -export interface GraphApiDocument { +export interface DocumentWithMemories { id: string title: string | null - summary: string | null - documentType: string + summary?: string | null + type: string createdAt: string updatedAt: string - x: number - y: number - memories: GraphApiMemory[] + memoryEntries: DocumentMemoryEntry[] } -export interface GraphApiEdge { - source: string - target: string - similarity: number -} - -export interface GraphViewportResponse { - documents: GraphApiDocument[] - edges: GraphApiEdge[] - viewport: { minX: number; maxX: number; minY: number; maxY: number } - totalCount: number -} - -export interface GraphBoundsResponse { - bounds: { - minX: number - maxX: number - minY: number - maxY: number - } | null +export interface DocumentsApiResponse { + documents: DocumentWithMemories[] + pagination: { + currentPage: number + limit: number + totalItems: number + totalPages: number + } } export function getMemoryText(m: Memory): string { @@ -332,53 +321,33 @@ export class SupermemoryClient { } } - // Fetch graph bounds for coordinate range - async getGraphBounds(containerTags?: string[]): Promise { - try { - const params = new URLSearchParams() - if (containerTags?.length) { - params.set("containerTags", JSON.stringify(containerTags)) - } - const url = `${this.apiUrl}/v3/graph/bounds${params.toString() ? `?${params}` : ""}` - const response = await fetch(url, { - method: "GET", - headers: { - Authorization: `Bearer ${this.bearerToken}`, - "Content-Type": "application/json", - }, - }) - if (!response.ok) { - throw Object.assign(new Error("Failed to fetch graph bounds"), { - status: response.status, - }) - } - return (await response.json()) as GraphBoundsResponse - } catch (error) { - this.handleError(error) - } - } - - // Fetch graph data for a viewport region - async getGraphViewport( - viewport: { minX: number; maxX: number; minY: number; maxY: number }, + // Fetch documents with their memory entries + async getDocuments( containerTags?: string[], + page = 1, limit = 200, - ): Promise { + ): Promise { try { - const response = await fetch(`${this.apiUrl}/v3/graph/viewport`, { + const response = await fetch(`${this.apiUrl}/v3/documents/documents`, { method: "POST", headers: { Authorization: `Bearer ${this.bearerToken}`, "Content-Type": "application/json", }, - body: JSON.stringify({ viewport, containerTags, limit }), + body: JSON.stringify({ + page, + limit, + sort: "createdAt", + order: "desc", + containerTags, + }), }) if (!response.ok) { - throw Object.assign(new Error("Failed to fetch graph viewport"), { + throw Object.assign(new Error("Failed to fetch documents"), { status: response.status, }) } - return (await response.json()) as GraphViewportResponse + return (await response.json()) as DocumentsApiResponse } catch (error) { this.handleError(error) } diff --git a/apps/mcp/src/server.ts b/apps/mcp/src/server.ts index 17f8ecd4..d387fde9 100644 --- a/apps/mcp/src/server.ts +++ b/apps/mcp/src/server.ts @@ -311,21 +311,14 @@ export class SupermemoryMCP extends McpAgent { ? [effectiveContainerTag] : undefined - const [bounds, viewport] = await Promise.all([ - client.getGraphBounds(containerTags), - client.getGraphViewport( - { minX: 0, maxX: 1000, minY: 0, maxY: 1000 }, - containerTags, - 200, - ), - ]) + const result = await client.getDocuments(containerTags, 1, 200) - const memoryCount = viewport.documents.reduce( - (sum, d) => sum + d.memories.length, + const memoryCount = result.documents.reduce( + (sum, d) => sum + d.memoryEntries.length, 0, ) const textParts = [ - `Memory Graph: ${viewport.documents.length} documents, ${memoryCount} memories, ${viewport.edges.length} connections`, + `Memory Graph: ${result.documents.length} documents, ${memoryCount} memories`, ] if (effectiveContainerTag) { textParts.push(`Project: ${effectiveContainerTag}`) @@ -335,10 +328,8 @@ export class SupermemoryMCP extends McpAgent { content: [{ type: "text" as const, text: textParts.join(". ") }], structuredContent: { containerTag: effectiveContainerTag, - bounds: bounds.bounds, - documents: viewport.documents, - edges: viewport.edges, - totalCount: viewport.totalCount, + documents: result.documents, + totalCount: result.pagination.totalItems, }, } } catch (error) { @@ -359,20 +350,15 @@ export class SupermemoryMCP extends McpAgent { }, ) - // App-only tool for the UI to fetch additional graph data + // App-only tool for the UI to fetch additional documents (pagination) registerAppTool( this.server, "fetch-graph-data", { - description: "Fetch graph data for a viewport region", + description: "Fetch documents with memories for graph display", inputSchema: z.object({ containerTag: z.string().optional(), - viewport: z.object({ - minX: z.number(), - maxX: z.number(), - minY: z.number(), - maxY: z.number(), - }), + page: z.number().optional().default(1), limit: z.number().optional().default(200), }), _meta: { @@ -385,12 +371,7 @@ export class SupermemoryMCP extends McpAgent { // @ts-expect-error - zod type inference issue with MCP SDK async (args: { containerTag?: string - viewport: { - minX: number - maxX: number - minY: number - maxY: number - } + page?: number limit?: number }) => { try { @@ -400,9 +381,9 @@ export class SupermemoryMCP extends McpAgent { const containerTags = effectiveContainerTag ? [effectiveContainerTag] : undefined - const data = await client.getGraphViewport( - args.viewport, + const data = await client.getDocuments( containerTags, + args.page, args.limit, ) diff --git a/apps/mcp/src/ui/mcp-app.ts b/apps/mcp/src/ui/mcp-app.ts index 1802cf82..d91532b5 100644 --- a/apps/mcp/src/ui/mcp-app.ts +++ b/apps/mcp/src/ui/mcp-app.ts @@ -27,11 +27,14 @@ interface GraphApiMemory { id: string memory: string 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 } @@ -43,22 +46,12 @@ interface GraphApiDocument { documentType: string createdAt: string updatedAt: string - x: number - y: number memories: GraphApiMemory[] } -interface GraphApiEdge { - source: string - target: string - similarity: number -} - interface ToolResultData { containerTag?: string - bounds: { minX: number; maxX: number; minY: number; maxY: number } | null documents: GraphApiDocument[] - edges: GraphApiEdge[] totalCount: number } @@ -91,8 +84,7 @@ type GraphNode = MemoryNode | DocumentNode interface GraphLink extends LinkObject { source: string | GraphNode target: string | GraphNode - edgeType: "doc-memory" | "version" | "similarity" - similarity?: number + edgeType: "doc-memory" | "version" | "same-space" } // ============================================================================= @@ -109,12 +101,12 @@ const EDGE_COLORS = { dark: { "doc-memory": "#4A5568", version: "#8B5CF6", - similarity: "#00D4B8", + "same-space": "#00D4B8", }, light: { "doc-memory": "#A0AEC0", version: "#8B5CF6", - similarity: "#0D9488", + "same-space": "#0D9488", }, } @@ -157,33 +149,20 @@ function getMemoryBorderColor(mem: GraphApiMemory): string { return MEMORY_BORDER.default } -function normalizeDocCoordinates( - documents: GraphApiDocument[], -): GraphApiDocument[] { - if (documents.length <= 1) return documents - - let minX = Number.POSITIVE_INFINITY - let maxX = Number.NEGATIVE_INFINITY - let minY = Number.POSITIVE_INFINITY - let maxY = Number.NEGATIVE_INFINITY - for (const doc of documents) { - minX = Math.min(minX, doc.x) - maxX = Math.max(maxX, doc.x) - minY = Math.min(minY, doc.y) - maxY = Math.max(maxY, doc.y) +/** Simple hash to get deterministic initial positions from doc ID */ +function hashCode(s: string): number { + let h = 0 + for (let i = 0; i < s.length; i++) { + h = (Math.imul(31, h) + s.charCodeAt(i)) | 0 } + return h +} - const rangeX = maxX - minX || 1 - const rangeY = maxY - minY || 1 - // Small spread so documents start near each other. - // The force simulation will naturally separate them. - const SPREAD = 50 - - return documents.map((doc) => ({ - ...doc, - x: ((doc.x - minX) / rangeX - 0.5) * SPREAD, - y: ((doc.y - minY) / rangeY - 0.5) * SPREAD, - })) +function initialPosition(id: string, spread: number): { x: number; y: number } { + const h = hashCode(id) + const angle = ((h & 0xffff) / 0xffff) * Math.PI * 2 + const radius = (((h >>> 16) & 0xffff) / 0xffff) * spread + return { x: Math.cos(angle) * radius, y: Math.sin(angle) * radius } } function transformData(data: ToolResultData): { @@ -193,10 +172,13 @@ function transformData(data: ToolResultData): { const nodes: GraphNode[] = [] const links: GraphLink[] = [] const nodeIds = new Set() + const SPREAD = 50 - const normalizedDocs = normalizeDocCoordinates(data.documents) + // Group documents by spaceId for same-space edges + const spaceGroups = new Map() - for (const doc of normalizedDocs) { + for (const doc of data.documents) { + const pos = initialPosition(doc.id, SPREAD) nodes.push({ id: doc.id, nodeType: "document", @@ -205,8 +187,8 @@ function transformData(data: ToolResultData): { docType: doc.documentType, createdAt: doc.createdAt, memoryCount: doc.memories.length, - x: doc.x, - y: doc.y, + x: pos.x, + y: pos.y, } as DocumentNode) nodeIds.add(doc.id) @@ -227,8 +209,8 @@ function transformData(data: ToolResultData): { parentMemoryId: mem.parentMemoryId, createdAt: mem.createdAt, borderColor: getMemoryBorderColor(mem), - x: doc.x + Math.cos(angle) * CLUSTER_SPREAD, - y: doc.y + Math.sin(angle) * CLUSTER_SPREAD, + x: pos.x + Math.cos(angle) * CLUSTER_SPREAD, + y: pos.y + Math.sin(angle) * CLUSTER_SPREAD, } as MemoryNode) nodeIds.add(mem.id) @@ -243,18 +225,32 @@ function transformData(data: ToolResultData): { edgeType: "version", }) } + + // Track space groups for same-space edges + if (mem.spaceId) { + const group = spaceGroups.get(mem.spaceId) + if (group) group.push(doc.id) + else spaceGroups.set(mem.spaceId, [doc.id]) + } } } - // Similarity edges from API - for (const edge of data.edges) { - if (nodeIds.has(edge.source) && nodeIds.has(edge.target)) { - links.push({ - source: edge.source, - target: edge.target, - edgeType: "similarity", - similarity: edge.similarity, - }) + // Same-space edges between documents sharing a space + const addedEdges = new Set() + for (const docIds of spaceGroups.values()) { + const unique = [...new Set(docIds)] + for (let i = 0; i < unique.length; i++) { + for (let j = i + 1; j < unique.length; j++) { + const key = `${unique[i]}:${unique[j]}` + if (!addedEdges.has(key)) { + addedEdges.add(key) + links.push({ + source: unique[i]!, + target: unique[j]!, + edgeType: "same-space", + }) + } + } } } @@ -371,13 +367,12 @@ const graph = new ForceGraph(container) ) .linkWidth((link: GraphLink) => { if (link.edgeType === "version") return 2 - if (link.edgeType === "similarity") - return 0.5 + (link.similarity || 0) * 1.5 + if (link.edgeType === "same-space") return 0.5 return 1 }) .linkColor(getLinkColor) .linkLineDash((link: GraphLink) => { - if (link.edgeType === "similarity") return [4, 2] + if (link.edgeType === "same-space") return [4, 2] return null as unknown as number[] }) .linkDirectionalArrowLength((link: GraphLink) => @@ -399,7 +394,7 @@ const graph = new ForceGraph(container) .strength((l: GraphLink) => { if (l.edgeType === "doc-memory") return 0.8 if (l.edgeType === "version") return 1.0 - return (l.similarity || 0.3) * 0.3 + return 0.15 // same-space }), ) .d3Force("collide", forceCollide(18)) diff --git a/apps/memory-graph-playground/src/app/page.tsx b/apps/memory-graph-playground/src/app/page.tsx index 124f6b27..68a6f945 100644 --- a/apps/memory-graph-playground/src/app/page.tsx +++ b/apps/memory-graph-playground/src/app/page.tsx @@ -21,13 +21,6 @@ interface DocumentsResponse { /** Convert the external API format to the internal graph format */ function toGraphDocuments(docs: DocumentWithMemories[]): GraphApiDocument[] { - // Use a seeded random for deterministic positions - let seed = 42 - const rand = () => { - seed = (seed * 16807 + 0) % 2147483647 - return seed / 2147483647 - } - return docs.map((doc) => ({ id: doc.id, title: doc.title, @@ -35,8 +28,6 @@ function toGraphDocuments(docs: DocumentWithMemories[]): GraphApiDocument[] { documentType: doc.documentType, createdAt: doc.createdAt, updatedAt: doc.updatedAt, - x: rand() * 1000, - y: rand() * 1000, memories: doc.memories.map( (mem): GraphApiMemory => ({ id: mem.id, @@ -136,7 +127,6 @@ export default function Home() { const data = generateMockGraphData({ documentCount: count, memoriesPerDoc: [2, 5], - similarityEdgeRatio: 0.05, seed: 12345, }) setMockData({ documents: data.documents }) diff --git a/apps/web/components/memory-graph/graph-card.tsx b/apps/web/components/memory-graph/graph-card.tsx index 841039ef..779b23fd 100644 --- a/apps/web/components/memory-graph/graph-card.tsx +++ b/apps/web/components/memory-graph/graph-card.tsx @@ -116,9 +116,8 @@ export const GraphCard = memo( ({ containerTags, width = 216, height = 220, className }) => { const { setViewMode } = useViewMode() - const { data, isLoading, error } = useGraphApi({ + const { documents, isLoading, error } = useGraphApi({ containerTags, - limit: 20, enabled: true, }) @@ -139,11 +138,8 @@ export const GraphCard = memo( ) } - const documentCount = data.stats?.documentsWithSpatial ?? 0 - const memoryCount = data.documents.reduce( - (sum, d) => sum + d.memories.length, - 0, - ) + const documentCount = documents.length + const memoryCount = documents.reduce((sum, d) => sum + d.memories.length, 0) return ( + )} + {!isLoading && !nodes.some((n) => n.type === "document") && children && (
{children}
)} diff --git a/packages/memory-graph/src/constants.ts b/packages/memory-graph/src/constants.ts index 49b2c667..91bdb5b7 100644 --- a/packages/memory-graph/src/constants.ts +++ b/packages/memory-graph/src/constants.ts @@ -47,10 +47,7 @@ export const DEFAULT_COLORS: GraphThemeColors = { textMuted: "#94a3b8", edgeDocMemory: "#4A5568", edgeVersion: "#8B5CF6", - edgeSimStrong: "#00D4B8", - edgeSimMedium: "#6B8FBF", - edgeSimWeak: "#4A6A8A", - edgeDocDoc: "#8DA3F4", + edgeSameSpace: "#4A6A8A", memBorderForgotten: "#EF4444", memBorderExpiring: "#F59E0B", memBorderRecent: "#10B981", diff --git a/packages/memory-graph/src/hooks/use-graph-data.ts b/packages/memory-graph/src/hooks/use-graph-data.ts index 91e37eca..93a39b74 100644 --- a/packages/memory-graph/src/hooks/use-graph-data.ts +++ b/packages/memory-graph/src/hooks/use-graph-data.ts @@ -2,7 +2,6 @@ import { useEffect, useMemo, useRef } from "react" import type { DocumentNodeData, GraphApiDocument, - GraphApiEdge, GraphApiMemory, GraphEdge, GraphNode, @@ -28,44 +27,34 @@ export function getMemoryBorderColor( return colors.memStrokeDefault } -export function getEdgeVisualProps(similarity: number) { - return { - opacity: 0.3 + similarity * 0.5, - thickness: 1 + similarity * 1.5, +export function getEdgeVisualProps(edgeType: string) { + switch (edgeType) { + case "doc-memory": + return { opacity: 0.3, thickness: 1.5 } + case "version": + return { opacity: 0.6, thickness: 2 } + case "same-space": + return { opacity: 0.15, thickness: 1 } + default: + return { opacity: 0.3, thickness: 1 } } } -export function normalizeDocCoordinates( - documents: GraphApiDocument[], -): GraphApiDocument[] { - if (documents.length <= 1) return documents - - let minX = Number.POSITIVE_INFINITY - let maxX = Number.NEGATIVE_INFINITY - let minY = Number.POSITIVE_INFINITY - let maxY = Number.NEGATIVE_INFINITY - - for (const doc of documents) { - minX = Math.min(minX, doc.x) - maxX = Math.max(maxX, doc.x) - minY = Math.min(minY, doc.y) - maxY = Math.max(maxY, doc.y) +/** + * Simple deterministic hash of a string to a number in [0, 1). + * Used for initial node placement so the force simulation has a + * deterministic starting layout. + */ +function hashToUnit(str: string): number { + let h = 0 + for (let i = 0; i < str.length; i++) { + h = (Math.imul(31, h) + str.charCodeAt(i)) | 0 } - - const rangeX = maxX - minX || 1 - const rangeY = maxY - minY || 1 - const PAD = 100 - - return documents.map((doc) => ({ - ...doc, - x: PAD + ((doc.x - minX) / rangeX) * (1000 - 2 * PAD), - y: PAD + ((doc.y - minY) / rangeY) * (1000 - 2 * PAD), - })) + return ((h >>> 0) % 10000) / 10000 } export function useGraphData( documents: GraphApiDocument[], - apiEdges: GraphApiEdge[], draggingNodeId: string | null, canvasWidth: number, canvasHeight: number, @@ -87,30 +76,20 @@ export function useGraphData( } }, [documents]) - const { scale, offsetX, offsetY } = useMemo(() => { - if (canvasWidth === 0 || canvasHeight === 0) { - return { scale: 1, offsetX: 0, offsetY: 0 } - } - const paddingFactor = 0.8 - const s = (Math.min(canvasWidth, canvasHeight) * paddingFactor) / 1000 - const ox = (canvasWidth - 1000 * s) / 2 - const oy = (canvasHeight - 1000 * s) / 2 - return { scale: s, offsetX: ox, offsetY: oy } - }, [canvasWidth, canvasHeight]) - - const normalizedDocs = useMemo( - () => normalizeDocCoordinates(documents), - [documents], - ) - const nodes = useMemo(() => { - if (!normalizedDocs || normalizedDocs.length === 0) return [] + if (!documents || documents.length === 0) return [] const result: GraphNode[] = [] + // Place nodes in the canvas space; force simulation will refine positions + const spreadW = Math.max(canvasWidth * 0.8, 400) + const spreadH = Math.max(canvasHeight * 0.8, 400) + const padX = (canvasWidth - spreadW) / 2 + const padY = (canvasHeight - spreadH) / 2 - for (const doc of normalizedDocs) { - const initialX = doc.x * scale + offsetX - const initialY = doc.y * scale + offsetY + for (const doc of documents) { + // Deterministic initial position based on doc id + const initialX = padX + hashToUnit(doc.id) * spreadW + const initialY = padY + hashToUnit(`${doc.id}-y`) * spreadH let docNode = nodeCache.current.get(doc.id) const docData: DocumentNodeData = { @@ -177,122 +156,81 @@ export function useGraphData( } return result - }, [normalizedDocs, scale, offsetX, offsetY, draggingNodeId, colors]) + }, [documents, canvasWidth, canvasHeight, draggingNodeId, colors]) const edges = useMemo(() => { - if (!normalizedDocs || normalizedDocs.length === 0) return [] + if (!documents || documents.length === 0) return [] const result: GraphEdge[] = [] - // Build allNodeIds from normalizedDocs directly to avoid depending on `nodes` - // (which changes identity on every render due to draggingNodeId/colors deps) const allNodeIds = new Set() - for (const doc of normalizedDocs) { + for (const doc of documents) { allNodeIds.add(doc.id) for (const mem of doc.memories) allNodeIds.add(mem.id) } - for (const doc of normalizedDocs) { + // Doc-memory edges + for (const doc of documents) { for (const mem of doc.memories) { result.push({ id: `dm-${doc.id}-${mem.id}`, source: doc.id, target: mem.id, - similarity: 1, - visualProps: { opacity: 0.3, thickness: 1.5 }, + visualProps: getEdgeVisualProps("doc-memory"), edgeType: "doc-memory", }) } } - for (const doc of normalizedDocs) { + // Version chain edges + for (const doc of documents) { for (const mem of doc.memories) { if (mem.parentMemoryId && allNodeIds.has(mem.parentMemoryId)) { result.push({ id: `ver-${mem.parentMemoryId}-${mem.id}`, source: mem.parentMemoryId, target: mem.id, - similarity: 1, - visualProps: { opacity: 0.6, thickness: 2 }, + visualProps: getEdgeVisualProps("version"), edgeType: "version", }) } } } - for (const apiEdge of apiEdges) { - if (!allNodeIds.has(apiEdge.source) || !allNodeIds.has(apiEdge.target)) { - continue + // Same-space edges: connect documents that share a spaceId + const spaceGroups = new Map() + for (const doc of documents) { + for (const mem of doc.memories) { + const group = spaceGroups.get(mem.spaceId) + if (group) { + if (!group.includes(doc.id)) group.push(doc.id) + } else { + spaceGroups.set(mem.spaceId, [doc.id]) + } + } + } + const addedPairs = new Set() + for (const docIds of spaceGroups.values()) { + for (let i = 0; i < docIds.length; i++) { + for (let j = i + 1; j < docIds.length; j++) { + const a = docIds[i]! + const b = docIds[j]! + const key = a < b ? `${a}:${b}` : `${b}:${a}` + if (!addedPairs.has(key)) { + addedPairs.add(key) + result.push({ + id: `ss-${key}`, + source: a, + target: b, + visualProps: getEdgeVisualProps("same-space"), + edgeType: "same-space", + }) + } + } } - - result.push({ - id: `sim-${apiEdge.source}-${apiEdge.target}`, - source: apiEdge.source, - target: apiEdge.target, - similarity: apiEdge.similarity, - visualProps: getEdgeVisualProps(apiEdge.similarity), - edgeType: "similarity", - }) } return result - }, [normalizedDocs, apiEdges]) + }, [documents]) - return { nodes, edges, scale, offsetX, offsetY } -} - -export function screenToBackendCoords( - screenX: number, - screenY: number, - panX: number, - panY: number, - zoom: number, - canvasWidth: number, - canvasHeight: number, -): { x: number; y: number } { - const canvasX = (screenX - panX) / zoom - const canvasY = (screenY - panY) / zoom - - const paddingFactor = 0.8 - const s = (Math.min(canvasWidth, canvasHeight) * paddingFactor) / 1000 - const ox = (canvasWidth - 1000 * s) / 2 - const oy = (canvasHeight - 1000 * s) / 2 - - return { - x: (canvasX - ox) / s, - y: (canvasY - oy) / s, - } -} - -export function calculateBackendViewport( - panX: number, - panY: number, - zoom: number, - canvasWidth: number, - canvasHeight: number, -): { minX: number; maxX: number; minY: number; maxY: number } { - const topLeft = screenToBackendCoords( - 0, - 0, - panX, - panY, - zoom, - canvasWidth, - canvasHeight, - ) - const bottomRight = screenToBackendCoords( - canvasWidth, - canvasHeight, - panX, - panY, - zoom, - canvasWidth, - canvasHeight, - ) - - return { - minX: Math.max(0, Math.min(topLeft.x, bottomRight.x)), - maxX: Math.max(topLeft.x, bottomRight.x), - minY: Math.max(0, Math.min(topLeft.y, bottomRight.y)), - maxY: Math.max(topLeft.y, bottomRight.y), - } + return { nodes, edges } } diff --git a/packages/memory-graph/src/hooks/use-graph-theme.ts b/packages/memory-graph/src/hooks/use-graph-theme.ts index bea72230..288dc0a9 100644 --- a/packages/memory-graph/src/hooks/use-graph-theme.ts +++ b/packages/memory-graph/src/hooks/use-graph-theme.ts @@ -37,19 +37,10 @@ function resolveColors(): GraphThemeColors { DEFAULT_COLORS.edgeDocMemory, ), edgeVersion: readCssVar("--graph-edge-version", DEFAULT_COLORS.edgeVersion), - edgeSimStrong: readCssVar( - "--graph-edge-sim-strong", - DEFAULT_COLORS.edgeSimStrong, + edgeSameSpace: readCssVar( + "--graph-edge-same-space", + DEFAULT_COLORS.edgeSameSpace, ), - edgeSimMedium: readCssVar( - "--graph-edge-sim-medium", - DEFAULT_COLORS.edgeSimMedium, - ), - edgeSimWeak: readCssVar( - "--graph-edge-sim-weak", - DEFAULT_COLORS.edgeSimWeak, - ), - edgeDocDoc: readCssVar("--graph-edge-doc-doc", DEFAULT_COLORS.edgeDocDoc), memBorderForgotten: readCssVar( "--graph-mem-border-forgotten", DEFAULT_COLORS.memBorderForgotten, diff --git a/packages/memory-graph/src/index.tsx b/packages/memory-graph/src/index.tsx index 7db8714d..fe6ea39b 100644 --- a/packages/memory-graph/src/index.tsx +++ b/packages/memory-graph/src/index.tsx @@ -25,9 +25,6 @@ export type { GraphApiDocument, GraphApiMemory, GraphApiEdge, - GraphViewportResponse, - GraphBoundsResponse, - GraphStatsResponse, DocumentNodeData, MemoryNodeData, ChainEntry, diff --git a/packages/memory-graph/src/mock-data.ts b/packages/memory-graph/src/mock-data.ts index 82ad10c9..a5c2cba7 100644 --- a/packages/memory-graph/src/mock-data.ts +++ b/packages/memory-graph/src/mock-data.ts @@ -1,9 +1,8 @@ -import type { GraphApiDocument, GraphApiEdge, GraphApiMemory } from "./types" +import type { GraphApiDocument, GraphApiMemory } from "./types" export interface MockGraphOptions { documentCount?: number memoriesPerDoc?: number | [number, number] - similarityEdgeRatio?: number seed?: number } @@ -246,12 +245,10 @@ function generateISODate( export function generateMockGraphData(options: MockGraphOptions = {}): { documents: GraphApiDocument[] - edges: GraphApiEdge[] } { const { documentCount = 100, memoriesPerDoc = [2, 6] as [number, number], - similarityEdgeRatio = 0.1, seed = 42, } = options @@ -371,10 +368,6 @@ export function generateMockGraphData(options: MockGraphOptions = {}): { }) } - // Position documents spread across a 1000x1000 space - const x = random() * 1000 - const y = random() * 1000 - documents.push({ id: docId, title: generateTitle(random), @@ -383,69 +376,9 @@ export function generateMockGraphData(options: MockGraphOptions = {}): { DOCUMENT_TYPES[Math.floor(random() * DOCUMENT_TYPES.length)], createdAt: docCreatedAt, updatedAt: docUpdatedAt, - x, - y, memories, }) } - // Generate similarity edges between random document pairs - const edges: GraphApiEdge[] = [] - const totalPossiblePairs = (documentCount * (documentCount - 1)) / 2 - const targetEdgeCount = Math.max( - 0, - Math.floor(totalPossiblePairs * similarityEdgeRatio), - ) - - // Use a set to avoid duplicate pairs - const edgeSet = new Set() - - // For small document counts, iterate all pairs; for large, sample randomly - if (documentCount <= 50 || targetEdgeCount > totalPossiblePairs * 0.5) { - // Iterate all pairs and include based on probability - for (let i = 0; i < documentCount; i++) { - for (let j = i + 1; j < documentCount; j++) { - if (random() < similarityEdgeRatio) { - const sourceId = documents[i].id - const targetId = documents[j].id - const key = `${sourceId}:${targetId}` - if (!edgeSet.has(key)) { - edgeSet.add(key) - // Similarity weighted towards medium-high values - const similarity = 0.3 + random() * 0.7 - edges.push({ - source: sourceId, - target: targetId, - similarity: Math.round(similarity * 1000) / 1000, - }) - } - } - } - } - } else { - // Random sampling for large document counts - let attempts = 0 - const maxAttempts = targetEdgeCount * 5 - while (edges.length < targetEdgeCount && attempts < maxAttempts) { - attempts++ - const i = Math.floor(random() * documentCount) - const j = Math.floor(random() * documentCount) - if (i === j) continue - const sourceIdx = Math.min(i, j) - const targetIdx = Math.max(i, j) - const sourceId = documents[sourceIdx].id - const targetId = documents[targetIdx].id - const key = `${sourceId}:${targetId}` - if (edgeSet.has(key)) continue - edgeSet.add(key) - const similarity = 0.3 + random() * 0.7 - edges.push({ - source: sourceId, - target: targetId, - similarity: Math.round(similarity * 1000) / 1000, - }) - } - } - - return { documents, edges } + return { documents } } diff --git a/packages/memory-graph/src/types.ts b/packages/memory-graph/src/types.ts index 1e564b98..6d76760a 100644 --- a/packages/memory-graph/src/types.ts +++ b/packages/memory-graph/src/types.ts @@ -23,42 +23,13 @@ export interface GraphApiDocument { documentType: string createdAt: string updatedAt: string - x: number - y: number memories: GraphApiMemory[] } export interface GraphApiEdge { source: string target: string - similarity: number -} - -export interface GraphViewportResponse { - documents: GraphApiDocument[] - edges: GraphApiEdge[] - viewport: { - minX: number - maxX: number - minY: number - maxY: number - } - totalCount: number -} - -export interface GraphBoundsResponse { - bounds: { - minX: number - maxX: number - minY: number - maxY: number - } | null -} - -export interface GraphStatsResponse { - totalDocuments: number - documentsWithSpatial: number - totalDocumentEdges: number + edgeType: "doc-memory" | "version" | "same-space" } // Typed node data @@ -111,12 +82,11 @@ export interface GraphEdge { id: string source: string | GraphNode target: string | GraphNode - similarity: number visualProps: { opacity: number thickness: number } - edgeType: "doc-memory" | "similarity" | "version" + edgeType: "doc-memory" | "version" | "same-space" } export interface GraphThemeColors { @@ -133,10 +103,7 @@ export interface GraphThemeColors { textMuted: string edgeDocMemory: string edgeVersion: string - edgeSimStrong: string - edgeSimMedium: string - edgeSimWeak: string - edgeDocDoc: string + edgeSameSpace: string memBorderForgotten: string memBorderExpiring: string memBorderRecent: string @@ -174,10 +141,14 @@ export interface GraphCanvasProps { export interface MemoryGraphProps { /** Documents to display - pass this for direct data mode */ documents?: GraphApiDocument[] - /** API edges between documents */ - apiEdges?: GraphApiEdge[] /** Whether data is loading */ isLoading?: boolean + /** Whether more data is being loaded */ + isLoadingMore?: boolean + /** Callback to load more documents */ + onLoadMore?: () => void + /** Whether there are more documents to load */ + hasMore?: boolean /** Error from data fetching */ error?: Error | null /** Children to render when no documents */