From 0b076297cc3c654cb441d5456de0d248e08ff66a Mon Sep 17 00:00:00 2001 From: Advait Paliwal Date: Wed, 24 Jun 2026 19:17:03 -0700 Subject: [PATCH] Make memory graph cluster colors configurable --- .../memory-graph/src/components/legend.tsx | 13 +++--- packages/memory-graph/src/constants.ts | 14 +++++++ .../memory-graph/src/hooks/use-graph-data.ts | 40 ++++++++++--------- .../memory-graph/src/hooks/use-graph-theme.ts | 21 +++++++++- packages/memory-graph/src/types.ts | 1 + 5 files changed, 63 insertions(+), 26 deletions(-) diff --git a/packages/memory-graph/src/components/legend.tsx b/packages/memory-graph/src/components/legend.tsx index 32c520af8..7d4b9d47c 100644 --- a/packages/memory-graph/src/components/legend.tsx +++ b/packages/memory-graph/src/components/legend.tsx @@ -81,13 +81,16 @@ function LineIcon({ ) } -function ClusterSwatches() { - const swatches = ["#58C7E8", "#E7BC52", "#74D680", "#D47B75", "#A789E8"] +function ClusterSwatches({ colors }: { colors: GraphThemeColors }) { + const swatches = + colors.clusterColors.length > 0 + ? colors.clusterColors.slice(0, 5) + : [colors.memStrokeDefault] return (
- {swatches.map((color) => ( + {swatches.map((color, index) => ( Color
- +
0 ? clusterColors : DEFAULT_CLUSTER_COLORS + return palette[hashString(key) % palette.length] as string } export function computeClusterAssignments( documents: GraphApiDocument[], + clusterColors: readonly string[] = DEFAULT_CLUSTER_COLORS, ): Map { const adjacency = new Map>() const docByMemory = new Map() @@ -180,7 +174,7 @@ export function computeClusterAssignments( : `relation:${firstDocId}:${firstId}` const assignment = { key, - color: getClusterColor(key), + color: getClusterColor(key, clusterColors), size: component.length, } @@ -205,6 +199,7 @@ function getMemoryRelationTargets(mem: GraphApiMemory): Record { function getDocumentClusterAssignment( doc: GraphApiDocument, assignments: Map, + clusterColors: readonly string[] = DEFAULT_CLUSTER_COLORS, ): ClusterAssignment { const counts = new Map< string, @@ -229,7 +224,7 @@ function getDocumentClusterAssignment( return ( best?.assignment ?? { key: `doc:${doc.id}`, - color: getClusterColor(`doc:${doc.id}`), + color: getClusterColor(`doc:${doc.id}`, clusterColors), size: 1, } ) @@ -492,7 +487,10 @@ export function useGraphData( ? buildAppendSpatialGrid(appendPlacementNodes) : null let appendIndex = 0 - const clusterAssignments = computeClusterAssignments(documents) + const clusterAssignments = computeClusterAssignments( + documents, + colors.clusterColors, + ) const result: GraphNode[] = [] // Spiral layout: documents form a compact spiral core, memories orbit @@ -509,7 +507,11 @@ export function useGraphData( for (let docIdx = 0; docIdx < docCount; docIdx++) { const doc = documents[docIdx] - const docCluster = getDocumentClusterAssignment(doc, clusterAssignments) + const docCluster = getDocumentClusterAssignment( + doc, + clusterAssignments, + colors.clusterColors, + ) const angle = docIdx * goldenAngle const radius = spiralScale * Math.sqrt((docIdx + 1) / docCount) const initialX = cx + Math.cos(angle) * radius diff --git a/packages/memory-graph/src/hooks/use-graph-theme.ts b/packages/memory-graph/src/hooks/use-graph-theme.ts index f045830a1..9f3cdb474 100644 --- a/packages/memory-graph/src/hooks/use-graph-theme.ts +++ b/packages/memory-graph/src/hooks/use-graph-theme.ts @@ -10,6 +10,19 @@ function readCssVar(name: string, fallback: string): string { return val || fallback } +function readCssColorList(name: string, fallback: string[]): string[] { + if (typeof document === "undefined") return fallback + const val = getComputedStyle(document.documentElement) + .getPropertyValue(name) + .trim() + if (!val) return fallback + const colors = val + .split(",") + .map((color) => color.trim()) + .filter(Boolean) + return colors.length > 0 ? colors : fallback +} + function resolveColors(): GraphThemeColors { return { bg: readCssVar("--graph-bg", DEFAULT_COLORS.bg), @@ -71,6 +84,10 @@ function resolveColors(): GraphThemeColors { "--graph-control-border", DEFAULT_COLORS.controlBorder, ), + clusterColors: readCssColorList( + "--graph-cluster-colors", + DEFAULT_COLORS.clusterColors, + ), } } @@ -107,13 +124,13 @@ export function useGraphTheme( const overrideKey = overrides ? Object.entries(overrides) .sort(([a], [b]) => a.localeCompare(b)) - .map(([k, v]) => `${k}:${v}`) + .map(([k, v]) => `${k}:${Array.isArray(v) ? v.join("|") : v}`) .join(",") : "" + // biome-ignore lint/correctness/useExhaustiveDependencies: overrideKey tracks overrides by value const merged = useMemo( () => (overrides ? { ...colors, ...overrides } : colors), - // biome-ignore lint/correctness/useExhaustiveDependencies: overrideKey tracks overrides by value [colors, overrideKey], ) diff --git a/packages/memory-graph/src/types.ts b/packages/memory-graph/src/types.ts index b446a0232..9fd640737 100644 --- a/packages/memory-graph/src/types.ts +++ b/packages/memory-graph/src/types.ts @@ -130,6 +130,7 @@ export interface GraphThemeColors { popoverTextMuted: string controlBg: string controlBorder: string + clusterColors: string[] } export interface GraphCanvasProps {