yangdx commited on
Commit
b6a03c6
·
1 Parent(s): f4c3104

refactor: improve graph property update mechanism

Browse files

- Move graph data and UI state update logic into store
- Ensure all property updates trigger PropertiesView refresh
- Add graphDataVersion dependency to PropertiesView
- Follow React best practices by avoiding direct UI state modification in utility functions
- Fix issue where non-entity_id property changes weren't reflected in UI

lightrag_webui/src/components/graph/PropertiesView.tsx CHANGED
@@ -16,10 +16,12 @@ const PropertiesView = () => {
16
  const focusedNode = useGraphStore.use.focusedNode()
17
  const selectedEdge = useGraphStore.use.selectedEdge()
18
  const focusedEdge = useGraphStore.use.focusedEdge()
 
19
 
20
  const [currentElement, setCurrentElement] = useState<NodeType | EdgeType | null>(null)
21
  const [currentType, setCurrentType] = useState<'node' | 'edge' | null>(null)
22
 
 
23
  useEffect(() => {
24
  let type: 'node' | 'edge' | null = null
25
  let element: RawNodeType | RawEdgeType | null = null
@@ -53,6 +55,7 @@ const PropertiesView = () => {
53
  selectedNode,
54
  focusedEdge,
55
  selectedEdge,
 
56
  setCurrentElement,
57
  setCurrentType,
58
  getNode,
 
16
  const focusedNode = useGraphStore.use.focusedNode()
17
  const selectedEdge = useGraphStore.use.selectedEdge()
18
  const focusedEdge = useGraphStore.use.focusedEdge()
19
+ const graphDataVersion = useGraphStore.use.graphDataVersion()
20
 
21
  const [currentElement, setCurrentElement] = useState<NodeType | EdgeType | null>(null)
22
  const [currentType, setCurrentType] = useState<'node' | 'edge' | null>(null)
23
 
24
+ // This effect will run when selection changes or when graph data is updated
25
  useEffect(() => {
26
  let type: 'node' | 'edge' | null = null
27
  let element: RawNodeType | RawEdgeType | null = null
 
55
  selectedNode,
56
  focusedEdge,
57
  selectedEdge,
58
+ graphDataVersion, // Add dependency on graphDataVersion to refresh when data changes
59
  setCurrentElement,
60
  setCurrentType,
61
  getNode,
lightrag_webui/src/stores/graph.ts CHANGED
@@ -31,6 +31,15 @@ export type RawEdgeType = {
31
  dynamicId: string
32
  }
33
 
 
 
 
 
 
 
 
 
 
34
  export class RawGraph {
35
  nodes: RawNodeType[] = []
36
  edges: RawEdgeType[] = []
@@ -127,9 +136,13 @@ interface GraphState {
127
  // Version counter to trigger data refresh
128
  graphDataVersion: number
129
  incrementGraphDataVersion: () => void
 
 
 
 
130
  }
131
 
132
- const useGraphStoreBase = create<GraphState>()((set) => ({
133
  selectedNode: null,
134
  focusedNode: null,
135
  selectedEdge: null,
@@ -234,6 +247,140 @@ const useGraphStoreBase = create<GraphState>()((set) => ({
234
  graphDataVersion: 0,
235
  incrementGraphDataVersion: () => set((state) => ({ graphDataVersion: state.graphDataVersion + 1 })),
236
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
237
  }))
238
 
239
  const useGraphStore = createSelectors(useGraphStoreBase)
 
31
  dynamicId: string
32
  }
33
 
34
+ /**
35
+ * Interface for tracking edges that need updating when a node ID changes
36
+ */
37
+ interface EdgeToUpdate {
38
+ originalDynamicId: string
39
+ newEdgeId: string
40
+ edgeIndex: number
41
+ }
42
+
43
  export class RawGraph {
44
  nodes: RawNodeType[] = []
45
  edges: RawEdgeType[] = []
 
136
  // Version counter to trigger data refresh
137
  graphDataVersion: number
138
  incrementGraphDataVersion: () => void
139
+
140
+ // Methods for updating graph elements and UI state together
141
+ updateNodeAndSelect: (nodeId: string, entityId: string, propertyName: string, newValue: string) => Promise<void>
142
+ updateEdgeAndSelect: (edgeId: string, dynamicId: string, sourceId: string, targetId: string, propertyName: string, newValue: string) => Promise<void>
143
  }
144
 
145
+ const useGraphStoreBase = create<GraphState>()((set, get) => ({
146
  selectedNode: null,
147
  focusedNode: null,
148
  selectedEdge: null,
 
247
  graphDataVersion: 0,
248
  incrementGraphDataVersion: () => set((state) => ({ graphDataVersion: state.graphDataVersion + 1 })),
249
 
250
+ // Methods for updating graph elements and UI state together
251
+ updateNodeAndSelect: async (nodeId: string, entityId: string, propertyName: string, newValue: string) => {
252
+ // Get current state
253
+ const state = get()
254
+ const { sigmaGraph, rawGraph } = state
255
+
256
+ // Validate graph state
257
+ if (!sigmaGraph || !rawGraph || !sigmaGraph.hasNode(nodeId)) {
258
+ return
259
+ }
260
+
261
+ try {
262
+ const nodeAttributes = sigmaGraph.getNodeAttributes(nodeId)
263
+
264
+ console.log('updateNodeAndSelect', nodeId, entityId, propertyName, newValue)
265
+
266
+ // For entity_id changes (node renaming) with NetworkX graph storage
267
+ if ((nodeId === entityId) && (propertyName === 'entity_id')) {
268
+ // Create new node with updated ID but same attributes
269
+ sigmaGraph.addNode(newValue, { ...nodeAttributes, label: newValue })
270
+
271
+ const edgesToUpdate: EdgeToUpdate[] = []
272
+
273
+ // Process all edges connected to this node
274
+ sigmaGraph.forEachEdge(nodeId, (edge, attributes, source, target) => {
275
+ const otherNode = source === nodeId ? target : source
276
+ const isOutgoing = source === nodeId
277
+
278
+ // Get original edge dynamic ID for later reference
279
+ const originalEdgeDynamicId = edge
280
+ const edgeIndexInRawGraph = rawGraph.edgeDynamicIdMap[originalEdgeDynamicId]
281
+
282
+ // Create new edge with updated node reference
283
+ const newEdgeId = sigmaGraph.addEdge(
284
+ isOutgoing ? newValue : otherNode,
285
+ isOutgoing ? otherNode : newValue,
286
+ attributes
287
+ )
288
+
289
+ // Track edges that need updating in the raw graph
290
+ if (edgeIndexInRawGraph !== undefined) {
291
+ edgesToUpdate.push({
292
+ originalDynamicId: originalEdgeDynamicId,
293
+ newEdgeId: newEdgeId,
294
+ edgeIndex: edgeIndexInRawGraph
295
+ })
296
+ }
297
+
298
+ // Remove the old edge
299
+ sigmaGraph.dropEdge(edge)
300
+ })
301
+
302
+ // Remove the old node after all edges are processed
303
+ sigmaGraph.dropNode(nodeId)
304
+
305
+ // Update node reference in raw graph data
306
+ const nodeIndex = rawGraph.nodeIdMap[nodeId]
307
+ if (nodeIndex !== undefined) {
308
+ rawGraph.nodes[nodeIndex].id = newValue
309
+ rawGraph.nodes[nodeIndex].labels = [newValue]
310
+ rawGraph.nodes[nodeIndex].properties.entity_id = newValue
311
+ delete rawGraph.nodeIdMap[nodeId]
312
+ rawGraph.nodeIdMap[newValue] = nodeIndex
313
+ }
314
+
315
+ // Update all edge references in raw graph data
316
+ edgesToUpdate.forEach(({ originalDynamicId, newEdgeId, edgeIndex }) => {
317
+ if (rawGraph.edges[edgeIndex]) {
318
+ // Update source/target references
319
+ if (rawGraph.edges[edgeIndex].source === nodeId) {
320
+ rawGraph.edges[edgeIndex].source = newValue
321
+ }
322
+ if (rawGraph.edges[edgeIndex].target === nodeId) {
323
+ rawGraph.edges[edgeIndex].target = newValue
324
+ }
325
+
326
+ // Update dynamic ID mappings
327
+ rawGraph.edges[edgeIndex].dynamicId = newEdgeId
328
+ delete rawGraph.edgeDynamicIdMap[originalDynamicId]
329
+ rawGraph.edgeDynamicIdMap[newEdgeId] = edgeIndex
330
+ }
331
+ })
332
+
333
+ // Update selected node in store
334
+ set({ selectedNode: newValue, moveToSelectedNode: true })
335
+ } else {
336
+ // For non-NetworkX nodes or non-entity_id changes
337
+ const nodeIndex = rawGraph.nodeIdMap[String(nodeId)]
338
+ if (nodeIndex !== undefined) {
339
+ rawGraph.nodes[nodeIndex].properties[propertyName] = newValue
340
+ if (propertyName === 'entity_id') {
341
+ rawGraph.nodes[nodeIndex].labels = [newValue]
342
+ sigmaGraph.setNodeAttribute(String(nodeId), 'label', newValue)
343
+ }
344
+ }
345
+
346
+ // Trigger a re-render by incrementing the version counter
347
+ set((state) => ({ graphDataVersion: state.graphDataVersion + 1 }))
348
+ }
349
+ } catch (error) {
350
+ console.error('Error updating node in graph:', error)
351
+ throw new Error('Failed to update node in graph')
352
+ }
353
+ },
354
+
355
+ updateEdgeAndSelect: async (edgeId: string, dynamicId: string, sourceId: string, targetId: string, propertyName: string, newValue: string) => {
356
+ // Get current state
357
+ const state = get()
358
+ const { sigmaGraph, rawGraph } = state
359
+
360
+ // Validate graph state
361
+ if (!sigmaGraph || !rawGraph) {
362
+ return
363
+ }
364
+
365
+ try {
366
+ const edgeIndex = rawGraph.edgeIdMap[String(edgeId)]
367
+ if (edgeIndex !== undefined && rawGraph.edges[edgeIndex]) {
368
+ rawGraph.edges[edgeIndex].properties[propertyName] = newValue
369
+ if(dynamicId !== undefined && propertyName === 'keywords') {
370
+ sigmaGraph.setEdgeAttribute(dynamicId, 'label', newValue)
371
+ }
372
+ }
373
+
374
+ // Trigger a re-render by incrementing the version counter
375
+ set((state) => ({ graphDataVersion: state.graphDataVersion + 1 }))
376
+
377
+ // Update selected edge in store to ensure UI reflects changes
378
+ set({ selectedEdge: dynamicId })
379
+ } catch (error) {
380
+ console.error(`Error updating edge ${sourceId}->${targetId} in graph:`, error)
381
+ throw new Error('Failed to update edge in graph')
382
+ }
383
+ }
384
  }))
385
 
386
  const useGraphStore = createSelectors(useGraphStoreBase)
lightrag_webui/src/utils/graphOperations.ts CHANGED
@@ -1,117 +1,18 @@
1
  import { useGraphStore } from '@/stores/graph'
2
 
3
- /**
4
- * Interface for tracking edges that need updating when a node ID changes
5
- */
6
- interface EdgeToUpdate {
7
- originalDynamicId: string
8
- newEdgeId: string
9
- edgeIndex: number
10
- }
11
-
12
  /**
13
  * Update node in the graph visualization
14
- * Handles both property updates and entity ID changes
15
- *
16
  * @param nodeId - ID of the node to update
 
17
  * @param propertyName - Name of the property being updated
18
  * @param newValue - New value for the property
19
  */
20
- export const updateGraphNode = async (nodeId: string, entityId:string, propertyName: string, newValue: string) => {
21
- // Get graph state from store
22
- const sigmaGraph = useGraphStore.getState().sigmaGraph
23
- const rawGraph = useGraphStore.getState().rawGraph
24
-
25
- // Validate graph state
26
- if (!sigmaGraph || !rawGraph || !sigmaGraph.hasNode(nodeId)) {
27
- return
28
- }
29
-
30
  try {
31
- const nodeAttributes = sigmaGraph.getNodeAttributes(nodeId)
32
-
33
- console.log('updateGraphNode', nodeId, entityId, propertyName, newValue)
34
-
35
- // For entity_id changes (node renaming) with NetworkX graph storage
36
- if ((nodeId === entityId) && (propertyName === 'entity_id')) {
37
- // Create new node with updated ID but same attributes
38
- sigmaGraph.addNode(newValue, { ...nodeAttributes, label: newValue })
39
-
40
- const edgesToUpdate: EdgeToUpdate[] = []
41
-
42
- // Process all edges connected to this node
43
- sigmaGraph.forEachEdge(nodeId, (edge, attributes, source, target) => {
44
- const otherNode = source === nodeId ? target : source
45
- const isOutgoing = source === nodeId
46
-
47
- // Get original edge dynamic ID for later reference
48
- const originalEdgeDynamicId = edge
49
- const edgeIndexInRawGraph = rawGraph.edgeDynamicIdMap[originalEdgeDynamicId]
50
-
51
- // Create new edge with updated node reference
52
- const newEdgeId = sigmaGraph.addEdge(
53
- isOutgoing ? newValue : otherNode,
54
- isOutgoing ? otherNode : newValue,
55
- attributes
56
- )
57
-
58
- // Track edges that need updating in the raw graph
59
- if (edgeIndexInRawGraph !== undefined) {
60
- edgesToUpdate.push({
61
- originalDynamicId: originalEdgeDynamicId,
62
- newEdgeId: newEdgeId,
63
- edgeIndex: edgeIndexInRawGraph
64
- })
65
- }
66
-
67
- // Remove the old edge
68
- sigmaGraph.dropEdge(edge)
69
- })
70
-
71
- // Remove the old node after all edges are processed
72
- sigmaGraph.dropNode(nodeId)
73
-
74
- // Update node reference in raw graph data
75
- const nodeIndex = rawGraph.nodeIdMap[nodeId]
76
- if (nodeIndex !== undefined) {
77
- rawGraph.nodes[nodeIndex].id = newValue
78
- rawGraph.nodes[nodeIndex].labels = [newValue]
79
- rawGraph.nodes[nodeIndex].properties.entity_id = newValue
80
- delete rawGraph.nodeIdMap[nodeId]
81
- rawGraph.nodeIdMap[newValue] = nodeIndex
82
- }
83
-
84
- // Update all edge references in raw graph data
85
- edgesToUpdate.forEach(({ originalDynamicId, newEdgeId, edgeIndex }) => {
86
- if (rawGraph.edges[edgeIndex]) {
87
- // Update source/target references
88
- if (rawGraph.edges[edgeIndex].source === nodeId) {
89
- rawGraph.edges[edgeIndex].source = newValue
90
- }
91
- if (rawGraph.edges[edgeIndex].target === nodeId) {
92
- rawGraph.edges[edgeIndex].target = newValue
93
- }
94
-
95
- // Update dynamic ID mappings
96
- rawGraph.edges[edgeIndex].dynamicId = newEdgeId
97
- delete rawGraph.edgeDynamicIdMap[originalDynamicId]
98
- rawGraph.edgeDynamicIdMap[newEdgeId] = edgeIndex
99
- }
100
- })
101
-
102
- // Update selected node in store
103
- useGraphStore.getState().setSelectedNode(newValue)
104
- } else {
105
- // for none NetworkX nodes or none entity_id changes
106
- const nodeIndex = rawGraph.nodeIdMap[String(nodeId)]
107
- if (nodeIndex !== undefined) {
108
- rawGraph.nodes[nodeIndex].properties[propertyName] = newValue
109
- if (propertyName === 'entity_id') {
110
- rawGraph.nodes[nodeIndex].labels = [newValue]
111
- sigmaGraph.setNodeAttribute(String(nodeId), 'label', newValue)
112
- }
113
- }
114
- }
115
  } catch (error) {
116
  console.error('Error updating node in graph:', error)
117
  throw new Error('Failed to update node in graph')
@@ -120,32 +21,19 @@ export const updateGraphNode = async (nodeId: string, entityId:string, propertyN
120
 
121
  /**
122
  * Update edge in the graph visualization
 
123
  *
 
 
124
  * @param sourceId - ID of the source node
125
  * @param targetId - ID of the target node
126
  * @param propertyName - Name of the property being updated
127
  * @param newValue - New value for the property
128
  */
129
  export const updateGraphEdge = async (edgeId: string, dynamicId: string, sourceId: string, targetId: string, propertyName: string, newValue: string) => {
130
- // Get graph state from store
131
- const sigmaGraph = useGraphStore.getState().sigmaGraph
132
- const rawGraph = useGraphStore.getState().rawGraph
133
-
134
- // Validate graph state
135
- if (!sigmaGraph || !rawGraph) {
136
- return
137
- }
138
-
139
  try {
140
-
141
- const edgeIndex = rawGraph.edgeIdMap[String(edgeId)]
142
- if (edgeIndex !== undefined && rawGraph.edges[edgeIndex]) {
143
- rawGraph.edges[edgeIndex].properties[propertyName] = newValue
144
- if(dynamicId !== undefined && propertyName === 'keywords') {
145
- sigmaGraph.setEdgeAttribute(dynamicId, 'label', newValue)
146
- }
147
- }
148
-
149
  } catch (error) {
150
  console.error(`Error updating edge ${sourceId}->${targetId} in graph:`, error)
151
  throw new Error('Failed to update edge in graph')
 
1
  import { useGraphStore } from '@/stores/graph'
2
 
 
 
 
 
 
 
 
 
 
3
  /**
4
  * Update node in the graph visualization
5
+ * This function is now a wrapper around the store's updateNodeAndSelect method
6
+ *
7
  * @param nodeId - ID of the node to update
8
+ * @param entityId - ID of the entity
9
  * @param propertyName - Name of the property being updated
10
  * @param newValue - New value for the property
11
  */
12
+ export const updateGraphNode = async (nodeId: string, entityId: string, propertyName: string, newValue: string) => {
 
 
 
 
 
 
 
 
 
13
  try {
14
+ // Call the store method that handles both data update and UI state
15
+ await useGraphStore.getState().updateNodeAndSelect(nodeId, entityId, propertyName, newValue)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  } catch (error) {
17
  console.error('Error updating node in graph:', error)
18
  throw new Error('Failed to update node in graph')
 
21
 
22
  /**
23
  * Update edge in the graph visualization
24
+ * This function is now a wrapper around the store's updateEdgeAndSelect method
25
  *
26
+ * @param edgeId - ID of the edge
27
+ * @param dynamicId - Dynamic ID of the edge in sigma graph
28
  * @param sourceId - ID of the source node
29
  * @param targetId - ID of the target node
30
  * @param propertyName - Name of the property being updated
31
  * @param newValue - New value for the property
32
  */
33
  export const updateGraphEdge = async (edgeId: string, dynamicId: string, sourceId: string, targetId: string, propertyName: string, newValue: string) => {
 
 
 
 
 
 
 
 
 
34
  try {
35
+ // Call the store method that handles both data update and UI state
36
+ await useGraphStore.getState().updateEdgeAndSelect(edgeId, dynamicId, sourceId, targetId, propertyName, newValue)
 
 
 
 
 
 
 
37
  } catch (error) {
38
  console.error(`Error updating edge ${sourceId}->${targetId} in graph:`, error)
39
  throw new Error('Failed to update edge in graph')