mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-09-13 23:11:10 +00:00
fix: use backend memoryRelations for edge computation in MCP app
Updated MCP app edge computation to use memoryRelations from backend instead of fabricating extends edges from spaceId grouping. Falls back to parentMemoryId for backwards compatibility.
This commit is contained in:
parent
46d851a2f8
commit
4de8b8f987
6 changed files with 111 additions and 79 deletions
|
|
@ -37,6 +37,8 @@ interface GraphApiMemory {
|
|||
rootMemoryId: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
relation?: "updates" | "extends" | "derives" | null
|
||||
memoryRelations?: Record<string, "updates" | "extends" | "derives"> | null
|
||||
}
|
||||
|
||||
interface GraphApiDocument {
|
||||
|
|
@ -174,9 +176,6 @@ function transformData(data: ToolResultData): {
|
|||
const nodeIds = new Set<string>()
|
||||
const SPREAD = 50
|
||||
|
||||
// Group documents by spaceId for extends edges
|
||||
const spaceGroups = new Map<string, string[]>()
|
||||
|
||||
for (const doc of data.documents) {
|
||||
const pos = initialPosition(doc.id, SPREAD)
|
||||
nodes.push({
|
||||
|
|
@ -214,42 +213,31 @@ function transformData(data: ToolResultData): {
|
|||
} as MemoryNode)
|
||||
nodeIds.add(mem.id)
|
||||
|
||||
// Derives link
|
||||
// Derives link (doc -> memory)
|
||||
links.push({ source: doc.id, target: mem.id, edgeType: "derives" })
|
||||
|
||||
// Updates link
|
||||
if (mem.parentMemoryId && nodeIds.has(mem.parentMemoryId)) {
|
||||
links.push({
|
||||
source: mem.parentMemoryId,
|
||||
target: mem.id,
|
||||
edgeType: "updates",
|
||||
})
|
||||
// Memory-to-memory relation edges from backend data.
|
||||
// Uses memoryRelations as primary source, falls back to parentMemoryId.
|
||||
let relations: Record<string, string> = {}
|
||||
if (
|
||||
mem.memoryRelations &&
|
||||
typeof mem.memoryRelations === "object" &&
|
||||
Object.keys(mem.memoryRelations).length > 0
|
||||
) {
|
||||
relations = mem.memoryRelations
|
||||
} else if (mem.parentMemoryId) {
|
||||
relations = { [mem.parentMemoryId]: "updates" }
|
||||
}
|
||||
|
||||
// Track space groups for extends edges
|
||||
if (mem.spaceId) {
|
||||
const group = spaceGroups.get(mem.spaceId)
|
||||
if (group) group.push(doc.id)
|
||||
else spaceGroups.set(mem.spaceId, [doc.id])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Extends edges between documents sharing a space
|
||||
const addedEdges = new Set<string>()
|
||||
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: "extends",
|
||||
})
|
||||
}
|
||||
for (const [targetId, relationType] of Object.entries(relations)) {
|
||||
if (!nodeIds.has(targetId)) continue
|
||||
const edgeType =
|
||||
relationType === "updates" ||
|
||||
relationType === "extends" ||
|
||||
relationType === "derives"
|
||||
? relationType
|
||||
: "updates"
|
||||
links.push({ source: targetId, target: mem.id, edgeType })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ interface UseGraphApiOptions {
|
|||
interface ApiMemoryEntry {
|
||||
id: string
|
||||
memory: string
|
||||
content?: string | null
|
||||
spaceId: string
|
||||
isStatic?: boolean
|
||||
isLatest?: boolean
|
||||
|
|
@ -29,6 +30,11 @@ interface ApiMemoryEntry {
|
|||
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 ApiDocument {
|
||||
|
|
@ -54,7 +60,7 @@ interface ApiDocumentsResponse {
|
|||
function toGraphMemory(mem: ApiMemoryEntry): GraphApiMemory {
|
||||
return {
|
||||
id: mem.id,
|
||||
memory: mem.memory,
|
||||
memory: mem.memory ?? mem.content ?? "",
|
||||
isStatic: mem.isStatic ?? false,
|
||||
spaceId: mem.spaceId ?? "",
|
||||
isLatest: mem.isLatest ?? true,
|
||||
|
|
@ -66,6 +72,11 @@ function toGraphMemory(mem: ApiMemoryEntry): GraphApiMemory {
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
export type MemoryRelation = "updates" | "extends" | "derives"
|
||||
|
||||
export interface MemoryEntry {
|
||||
id: string
|
||||
content: string
|
||||
content: string | null
|
||||
memory?: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
spaceId?: string
|
||||
spaceId?: string | null
|
||||
embedding?: number[]
|
||||
isStatic?: boolean
|
||||
isForgotten?: boolean
|
||||
|
|
@ -13,6 +16,16 @@ export interface MemoryEntry {
|
|||
parentMemoryId?: string | null
|
||||
rootMemoryId?: string | null
|
||||
isLatest?: boolean
|
||||
// Relation fields from backend
|
||||
relation?: MemoryRelation | null
|
||||
updatesMemoryId?: string | null
|
||||
nextVersionId?: string | null
|
||||
memoryRelations?: Record<string, MemoryRelation> | null
|
||||
// Source/join fields
|
||||
sourceAddedAt?: string | null
|
||||
sourceRelevanceScore?: number | null
|
||||
sourceMetadata?: Record<string, unknown> | null
|
||||
spaceContainerTag?: string | null
|
||||
}
|
||||
|
||||
export interface DocumentWithMemories {
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ export function useGraphData(
|
|||
for (const mem of doc.memories) allNodeIds.add(mem.id)
|
||||
}
|
||||
|
||||
// Derives edges (doc -> memory)
|
||||
// 1. Derives edges: document -> memory (structural)
|
||||
for (const doc of documents) {
|
||||
for (const mem of doc.memories) {
|
||||
result.push({
|
||||
|
|
@ -193,50 +193,39 @@ export function useGraphData(
|
|||
}
|
||||
}
|
||||
|
||||
// Updates edges (version chain)
|
||||
// 2. Memory-to-memory relation edges from backend data.
|
||||
// Uses memoryRelations (Record<targetId, relationType>) as primary source,
|
||||
// falls back to parentMemoryId for legacy data.
|
||||
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,
|
||||
visualProps: getEdgeVisualProps("updates"),
|
||||
edgeType: "updates",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
let relations: Record<string, string> = {}
|
||||
|
||||
// Extends edges: connect documents that share a spaceId
|
||||
const spaceGroups = new Map<string, string[]>()
|
||||
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])
|
||||
if (
|
||||
mem.memoryRelations &&
|
||||
typeof mem.memoryRelations === "object" &&
|
||||
Object.keys(mem.memoryRelations).length > 0
|
||||
) {
|
||||
relations = mem.memoryRelations
|
||||
} else if (mem.parentMemoryId) {
|
||||
// Legacy fallback: parentMemoryId implies "updates"
|
||||
relations = { [mem.parentMemoryId]: "updates" }
|
||||
}
|
||||
}
|
||||
}
|
||||
const addedPairs = new Set<string>()
|
||||
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("extends"),
|
||||
edgeType: "extends",
|
||||
})
|
||||
}
|
||||
|
||||
for (const [targetId, relationType] of Object.entries(relations)) {
|
||||
if (!allNodeIds.has(targetId)) continue
|
||||
const edgeType =
|
||||
relationType === "updates" ||
|
||||
relationType === "extends" ||
|
||||
relationType === "derives"
|
||||
? relationType
|
||||
: "updates"
|
||||
result.push({
|
||||
id: `rel-${targetId}-${mem.id}`,
|
||||
source: targetId,
|
||||
target: mem.id,
|
||||
visualProps: getEdgeVisualProps(edgeType),
|
||||
edgeType,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -323,6 +323,8 @@ export function generateMockGraphData(options: MockGraphOptions = {}): {
|
|||
let version = 1
|
||||
let isLatest = true
|
||||
let isForgotten = false
|
||||
let memoryRelations: Record<string, "updates" | "extends" | "derives"> =
|
||||
{}
|
||||
|
||||
// Build version chain for first few memories if applicable
|
||||
if (hasVersionChain && m < 3) {
|
||||
|
|
@ -342,12 +344,27 @@ export function generateMockGraphData(options: MockGraphOptions = {}): {
|
|||
chainPrevId = memId
|
||||
isLatest = m === 2 // last in the 3-memory chain
|
||||
isForgotten = !isLatest && random() < 0.2
|
||||
// Add "updates" relation to parent
|
||||
if (parentMemoryId) {
|
||||
memoryRelations = { [parentMemoryId]: "updates" }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Standalone memory
|
||||
isForgotten = random() < 0.1
|
||||
isLatest = true
|
||||
version = 1
|
||||
// Randomly add extends/derives relations to earlier memories in this doc
|
||||
if (m > 0 && random() < 0.2) {
|
||||
const targetIdx = Math.floor(random() * m)
|
||||
const targetMem = memories[targetIdx]
|
||||
if (targetMem) {
|
||||
const relType = random() < 0.5 ? "extends" : "derives"
|
||||
memoryRelations = {
|
||||
[targetMem.id]: relType as "extends" | "derives",
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Determine forgetAfter (for some non-forgotten memories, set a future expiry)
|
||||
|
|
@ -376,6 +393,8 @@ export function generateMockGraphData(options: MockGraphOptions = {}): {
|
|||
rootMemoryId,
|
||||
createdAt: memCreatedAt,
|
||||
updatedAt: memUpdatedAt,
|
||||
memoryRelations:
|
||||
Object.keys(memoryRelations).length > 0 ? memoryRelations : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
// Graph API types matching backend response
|
||||
|
||||
export type MemoryRelation = "updates" | "extends" | "derives"
|
||||
|
||||
export interface GraphApiMemory {
|
||||
id: string
|
||||
memory: string
|
||||
content?: string | null
|
||||
isStatic: boolean
|
||||
spaceId: string
|
||||
isLatest: boolean
|
||||
|
|
@ -14,6 +17,13 @@ export interface GraphApiMemory {
|
|||
rootMemoryId: string | null
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
// Relation fields from backend
|
||||
relation?: MemoryRelation | null
|
||||
updatesMemoryId?: string | null
|
||||
nextVersionId?: string | null
|
||||
memoryRelations?: Record<string, MemoryRelation> | null
|
||||
// Source/join fields
|
||||
spaceContainerTag?: string | null
|
||||
}
|
||||
|
||||
export interface GraphApiDocument {
|
||||
|
|
@ -59,6 +69,8 @@ export interface MemoryNodeData {
|
|||
spaceId: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
relation?: MemoryRelation | null
|
||||
memoryRelations?: Record<string, MemoryRelation> | null
|
||||
}
|
||||
|
||||
export interface GraphNode {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue