choizhang commited on
Commit
03cc6ab
·
1 Parent(s): d7362f8

refactor(graph): Refactoring the attribute line component to extract common logic into a separate file

Browse files
lightrag_webui/src/components/graph/EditablePropertyRow.tsx CHANGED
@@ -1,22 +1,18 @@
1
  import { useState, useEffect } from 'react'
2
- import { useRef } from 'react'
3
  import { useTranslation } from 'react-i18next'
4
- import Text from '@/components/ui/Text'
5
  import { toast } from 'sonner'
6
  import { updateEntity, updateRelation, checkEntityNameExists } from '@/api/lightrag'
7
- import { useGraphStore } from '@/stores/graph'
8
- import { PencilIcon } from 'lucide-react'
9
  import PropertyEditDialog from './PropertyEditDialog'
10
 
11
  /**
12
  * Interface for the EditablePropertyRow component props
13
- * Defines all possible properties that can be passed to the component
14
  */
15
  interface EditablePropertyRowProps {
16
  name: string // Property name to display and edit
17
  value: any // Initial value of the property
18
  onClick?: () => void // Optional click handler for the property value
19
- tooltip?: string // Optional tooltip text
20
  entityId?: string // ID of the entity (for node type)
21
  entityType?: 'node' | 'edge' // Type of graph entity
22
  sourceId?: string // Source node ID (for edge type)
@@ -26,19 +22,8 @@ interface EditablePropertyRowProps {
26
  }
27
 
28
  /**
29
- * Interface for tracking edges that need updating when a node ID changes
30
- */
31
- interface EdgeToUpdate {
32
- originalDynamicId: string;
33
- newEdgeId: string;
34
- edgeIndex: number;
35
- }
36
-
37
- /**
38
- * EditablePropertyRow component that supports double-click to edit property values
39
  * This component is used in the graph properties panel to display and edit entity properties
40
- *
41
- * @component
42
  */
43
  const EditablePropertyRow = ({
44
  name,
@@ -51,253 +36,27 @@ const EditablePropertyRow = ({
51
  onValueChange,
52
  isEditable = false
53
  }: EditablePropertyRowProps) => {
54
- // Component state
55
  const { t } = useTranslation()
56
  const [isEditing, setIsEditing] = useState(false)
57
  const [isSubmitting, setIsSubmitting] = useState(false)
58
  const [currentValue, setCurrentValue] = useState(initialValue)
59
- const inputRef = useRef<HTMLInputElement>(null)
60
 
61
-
62
- /**
63
- * Update currentValue when initialValue changes from parent
64
- */
65
  useEffect(() => {
66
  setCurrentValue(initialValue)
67
  }, [initialValue])
68
 
69
- /**
70
- * Initialize edit value and focus input when entering edit mode
71
- */
72
- useEffect(() => {
73
- if (isEditing) {
74
- // Focus the input element when entering edit mode with a small delay
75
- // to ensure the input is rendered before focusing
76
- setTimeout(() => {
77
- if (inputRef.current) {
78
- inputRef.current.focus()
79
- inputRef.current.select()
80
- }
81
- }, 50)
82
- }
83
- }, [isEditing])
84
-
85
- /**
86
- * Get translated property name from i18n
87
- * Falls back to the original name if no translation is found
88
- */
89
- const getPropertyNameTranslation = (propName: string) => {
90
- const translationKey = `graphPanel.propertiesView.node.propertyNames.${propName}`
91
- const translation = t(translationKey)
92
- return translation === translationKey ? propName : translation
93
- }
94
-
95
- /**
96
- * Handle edit icon click to open dialog
97
- */
98
  const handleEditClick = () => {
99
  if (isEditable && !isEditing) {
100
  setIsEditing(true)
101
  }
102
  }
103
 
104
- /**
105
- * Handle dialog close without saving
106
- */
107
  const handleCancel = () => {
108
  setIsEditing(false)
109
  }
110
 
111
- /**
112
- * Update node in the graph visualization after API update
113
- * Handles both property updates and entity ID changes
114
- *
115
- * @param nodeId - ID of the node to update
116
- * @param propertyName - Name of the property being updated
117
- * @param newValue - New value for the property
118
- */
119
- const updateGraphNode = async (nodeId: string, propertyName: string, newValue: string) => {
120
- // Get graph state from store
121
- const sigmaInstance = useGraphStore.getState().sigmaInstance
122
- const sigmaGraph = useGraphStore.getState().sigmaGraph
123
- const rawGraph = useGraphStore.getState().rawGraph
124
-
125
- // Validate graph state
126
- if (!sigmaInstance || !sigmaGraph || !rawGraph || !sigmaGraph.hasNode(String(nodeId))) {
127
- return
128
- }
129
-
130
- try {
131
- const nodeAttributes = sigmaGraph.getNodeAttributes(String(nodeId))
132
-
133
- // Special handling for entity_id changes (node renaming)
134
- if (propertyName === 'entity_id') {
135
- // Create new node with updated ID but same attributes
136
- sigmaGraph.addNode(newValue, { ...nodeAttributes, label: newValue })
137
-
138
- const edgesToUpdate: EdgeToUpdate[] = [];
139
-
140
- // Process all edges connected to this node
141
- sigmaGraph.forEachEdge(String(nodeId), (edge, attributes, source, target) => {
142
- const otherNode = source === String(nodeId) ? target : source
143
- const isOutgoing = source === String(nodeId)
144
-
145
- // Get original edge dynamic ID for later reference
146
- const originalEdgeDynamicId = edge
147
- const edgeIndexInRawGraph = rawGraph.edgeDynamicIdMap[originalEdgeDynamicId]
148
-
149
- // Create new edge with updated node reference
150
- const newEdgeId = sigmaGraph.addEdge(
151
- isOutgoing ? newValue : otherNode,
152
- isOutgoing ? otherNode : newValue,
153
- attributes
154
- )
155
-
156
- // Track edges that need updating in the raw graph
157
- if (edgeIndexInRawGraph !== undefined) {
158
- edgesToUpdate.push({
159
- originalDynamicId: originalEdgeDynamicId,
160
- newEdgeId: newEdgeId,
161
- edgeIndex: edgeIndexInRawGraph
162
- })
163
- }
164
-
165
- // Remove the old edge
166
- sigmaGraph.dropEdge(edge)
167
- })
168
-
169
- // Remove the old node after all edges are processed
170
- sigmaGraph.dropNode(String(nodeId))
171
-
172
- // Update node reference in raw graph data
173
- const nodeIndex = rawGraph.nodeIdMap[String(nodeId)]
174
- if (nodeIndex !== undefined) {
175
- rawGraph.nodes[nodeIndex].id = newValue
176
- rawGraph.nodes[nodeIndex].properties.entity_id = newValue
177
- delete rawGraph.nodeIdMap[String(nodeId)]
178
- rawGraph.nodeIdMap[newValue] = nodeIndex
179
- }
180
-
181
- // Update all edge references in raw graph data
182
- edgesToUpdate.forEach(({ originalDynamicId, newEdgeId, edgeIndex }) => {
183
- if (rawGraph.edges[edgeIndex]) {
184
- // Update source/target references
185
- if (rawGraph.edges[edgeIndex].source === String(nodeId)) {
186
- rawGraph.edges[edgeIndex].source = newValue
187
- }
188
- if (rawGraph.edges[edgeIndex].target === String(nodeId)) {
189
- rawGraph.edges[edgeIndex].target = newValue
190
- }
191
-
192
- // Update dynamic ID mappings
193
- rawGraph.edges[edgeIndex].dynamicId = newEdgeId
194
- delete rawGraph.edgeDynamicIdMap[originalDynamicId]
195
- rawGraph.edgeDynamicIdMap[newEdgeId] = edgeIndex
196
- }
197
- })
198
-
199
- // Update selected node in store
200
- useGraphStore.getState().setSelectedNode(newValue)
201
- } else {
202
- // For other properties, just update the property in raw graph
203
- const nodeIndex = rawGraph.nodeIdMap[String(nodeId)]
204
- if (nodeIndex !== undefined) {
205
- rawGraph.nodes[nodeIndex].properties[propertyName] = newValue
206
- }
207
- }
208
- } catch (error) {
209
- console.error('Error updating node in graph:', error)
210
- throw new Error('Failed to update node in graph')
211
- }
212
- }
213
-
214
- /**
215
- * Update edge in the graph visualization after API update
216
- *
217
- * @param sourceId - ID of the source node
218
- * @param targetId - ID of the target node
219
- * @param propertyName - Name of the property being updated
220
- * @param newValue - New value for the property
221
- */
222
- const updateGraphEdge = async (sourceId: string, targetId: string, propertyName: string, newValue: string) => {
223
- // Get graph state from store
224
- const sigmaInstance = useGraphStore.getState().sigmaInstance
225
- const sigmaGraph = useGraphStore.getState().sigmaGraph
226
- const rawGraph = useGraphStore.getState().rawGraph
227
-
228
- // Validate graph state
229
- if (!sigmaInstance || !sigmaGraph || !rawGraph) {
230
- return
231
- }
232
-
233
- try {
234
- // Find the edge between source and target nodes
235
- const allEdges = sigmaGraph.edges()
236
- let keyToUse = null
237
-
238
- for (const edge of allEdges) {
239
- const edgeSource = sigmaGraph.source(edge)
240
- const edgeTarget = sigmaGraph.target(edge)
241
-
242
- // Match edge in either direction (undirected graph support)
243
- if ((edgeSource === sourceId && edgeTarget === targetId) ||
244
- (edgeSource === targetId && edgeTarget === sourceId)) {
245
- keyToUse = edge
246
- break
247
- }
248
- }
249
-
250
- if (keyToUse !== null) {
251
- // Special handling for keywords property (updates edge label)
252
- if(propertyName === 'keywords') {
253
- sigmaGraph.setEdgeAttribute(keyToUse, 'label', newValue);
254
- } else {
255
- sigmaGraph.setEdgeAttribute(keyToUse, propertyName, newValue);
256
- }
257
-
258
- // Update edge in raw graph data using dynamic ID mapping
259
- if (keyToUse && rawGraph.edgeDynamicIdMap[keyToUse] !== undefined) {
260
- const edgeIndex = rawGraph.edgeDynamicIdMap[keyToUse];
261
- if (rawGraph.edges[edgeIndex]) {
262
- rawGraph.edges[edgeIndex].properties[propertyName] = newValue;
263
- } else {
264
- console.warn(`Edge index ${edgeIndex} found but edge data missing in rawGraph for dynamicId ${entityId}`);
265
- }
266
- } else {
267
- // Fallback: try to find edge by key in edge ID map
268
- console.warn(`Could not find edge with dynamicId ${entityId} in rawGraph.edgeDynamicIdMap to update properties.`);
269
- if (keyToUse !== null) {
270
- const edgeIndexByKey = rawGraph.edgeIdMap[keyToUse];
271
- if (edgeIndexByKey !== undefined && rawGraph.edges[edgeIndexByKey]) {
272
- rawGraph.edges[edgeIndexByKey].properties[propertyName] = newValue;
273
- console.log(`Updated rawGraph edge using constructed key ${keyToUse}`);
274
- } else {
275
- console.warn(`Could not find edge in rawGraph using key ${keyToUse} either.`);
276
- }
277
- } else {
278
- console.warn('Cannot update edge properties: edge key is null');
279
- }
280
- }
281
- } else {
282
- console.warn(`Edge not found in sigmaGraph with key ${keyToUse}`);
283
- }
284
- } catch (error) {
285
- // Log the specific edge key that caused the error
286
- console.error(`Error updating edge ${sourceId}->${targetId} in graph:`, error);
287
- throw new Error('Failed to update edge in graph')
288
- }
289
- }
290
-
291
- /**
292
- * Save changes to the property value
293
- * Updates both the API and the graph visualization
294
- */
295
  const handleSave = async (value: string) => {
296
- // Prevent duplicate submissions
297
- if (isSubmitting) return
298
-
299
- // Skip if value hasn't changed
300
- if (value === String(currentValue)) {
301
  setIsEditing(false)
302
  return
303
  }
@@ -305,47 +64,31 @@ const EditablePropertyRow = ({
305
  setIsSubmitting(true)
306
 
307
  try {
308
- // Handle node property updates
309
  if (entityType === 'node' && entityId) {
310
  let updatedData = { [name]: value }
311
 
312
- // Special handling for entity_id (name) changes
313
  if (name === 'entity_id') {
314
- // Check if the new name already exists
315
  const exists = await checkEntityNameExists(value)
316
  if (exists) {
317
  toast.error(t('graphPanel.propertiesView.errors.duplicateName'))
318
- setIsSubmitting(false)
319
  return
320
  }
321
- // For entity_id, we update entity_name in the API
322
  updatedData = { 'entity_name': value }
323
  }
324
 
325
- // Update entity in API
326
  await updateEntity(entityId, updatedData, true)
327
- // Update graph visualization
328
  await updateGraphNode(entityId, name, value)
329
  toast.success(t('graphPanel.propertiesView.success.entityUpdated'))
330
- }
331
- // Handle edge property updates
332
- else if (entityType === 'edge' && sourceId && targetId) {
333
  const updatedData = { [name]: value }
334
- // Update relation in API
335
  await updateRelation(sourceId, targetId, updatedData)
336
- // Update graph visualization
337
  await updateGraphEdge(sourceId, targetId, name, value)
338
  toast.success(t('graphPanel.propertiesView.success.relationUpdated'))
339
  }
340
 
341
- // Update local state
342
  setIsEditing(false)
343
  setCurrentValue(value)
344
-
345
- // Notify parent component if callback provided
346
- if (onValueChange) {
347
- onValueChange(value)
348
- }
349
  } catch (error) {
350
  console.error('Error updating property:', error)
351
  toast.error(t('graphPanel.propertiesView.errors.updateFailed'))
@@ -354,37 +97,11 @@ const EditablePropertyRow = ({
354
  }
355
  }
356
 
357
- /**
358
- * Render the property row with edit functionality
359
- * Shows property name, edit icon, and the current value
360
- */
361
  return (
362
  <div className="flex items-center gap-1">
363
- {/* Property name with translation */}
364
- <span className="text-primary/60 tracking-wide whitespace-nowrap">
365
- {getPropertyNameTranslation(name)}
366
- </span>
367
-
368
- {/* Edit icon without tooltip */}
369
- <div>
370
- <PencilIcon
371
- className="h-3 w-3 text-gray-500 hover:text-gray-700 cursor-pointer"
372
- onClick={handleEditClick}
373
- />
374
- </div>:
375
-
376
- {/* Text display */}
377
- <div className="flex items-center gap-1">
378
- <Text
379
- className="hover:bg-primary/20 rounded p-1 overflow-hidden text-ellipsis"
380
- tooltipClassName="max-w-80"
381
- text={currentValue}
382
- side="left"
383
- onClick={onClick}
384
- />
385
- </div>
386
-
387
- {/* Edit dialog */}
388
  <PropertyEditDialog
389
  isOpen={isEditing}
390
  onClose={handleCancel}
 
1
  import { useState, useEffect } from 'react'
 
2
  import { useTranslation } from 'react-i18next'
 
3
  import { toast } from 'sonner'
4
  import { updateEntity, updateRelation, checkEntityNameExists } from '@/api/lightrag'
5
+ import { updateGraphNode, updateGraphEdge } from '@/utils/graphOperations'
6
+ import { PropertyName, EditIcon, PropertyValue } from './PropertyRowComponents'
7
  import PropertyEditDialog from './PropertyEditDialog'
8
 
9
  /**
10
  * Interface for the EditablePropertyRow component props
 
11
  */
12
  interface EditablePropertyRowProps {
13
  name: string // Property name to display and edit
14
  value: any // Initial value of the property
15
  onClick?: () => void // Optional click handler for the property value
 
16
  entityId?: string // ID of the entity (for node type)
17
  entityType?: 'node' | 'edge' // Type of graph entity
18
  sourceId?: string // Source node ID (for edge type)
 
22
  }
23
 
24
  /**
25
+ * EditablePropertyRow component that supports editing property values
 
 
 
 
 
 
 
 
 
26
  * This component is used in the graph properties panel to display and edit entity properties
 
 
27
  */
28
  const EditablePropertyRow = ({
29
  name,
 
36
  onValueChange,
37
  isEditable = false
38
  }: EditablePropertyRowProps) => {
 
39
  const { t } = useTranslation()
40
  const [isEditing, setIsEditing] = useState(false)
41
  const [isSubmitting, setIsSubmitting] = useState(false)
42
  const [currentValue, setCurrentValue] = useState(initialValue)
 
43
 
 
 
 
 
44
  useEffect(() => {
45
  setCurrentValue(initialValue)
46
  }, [initialValue])
47
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  const handleEditClick = () => {
49
  if (isEditable && !isEditing) {
50
  setIsEditing(true)
51
  }
52
  }
53
 
 
 
 
54
  const handleCancel = () => {
55
  setIsEditing(false)
56
  }
57
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
  const handleSave = async (value: string) => {
59
+ if (isSubmitting || value === String(currentValue)) {
 
 
 
 
60
  setIsEditing(false)
61
  return
62
  }
 
64
  setIsSubmitting(true)
65
 
66
  try {
 
67
  if (entityType === 'node' && entityId) {
68
  let updatedData = { [name]: value }
69
 
 
70
  if (name === 'entity_id') {
 
71
  const exists = await checkEntityNameExists(value)
72
  if (exists) {
73
  toast.error(t('graphPanel.propertiesView.errors.duplicateName'))
 
74
  return
75
  }
 
76
  updatedData = { 'entity_name': value }
77
  }
78
 
 
79
  await updateEntity(entityId, updatedData, true)
 
80
  await updateGraphNode(entityId, name, value)
81
  toast.success(t('graphPanel.propertiesView.success.entityUpdated'))
82
+ } else if (entityType === 'edge' && sourceId && targetId) {
 
 
83
  const updatedData = { [name]: value }
 
84
  await updateRelation(sourceId, targetId, updatedData)
 
85
  await updateGraphEdge(sourceId, targetId, name, value)
86
  toast.success(t('graphPanel.propertiesView.success.relationUpdated'))
87
  }
88
 
 
89
  setIsEditing(false)
90
  setCurrentValue(value)
91
+ onValueChange?.(value)
 
 
 
 
92
  } catch (error) {
93
  console.error('Error updating property:', error)
94
  toast.error(t('graphPanel.propertiesView.errors.updateFailed'))
 
97
  }
98
  }
99
 
 
 
 
 
100
  return (
101
  <div className="flex items-center gap-1">
102
+ <PropertyName name={name} />
103
+ <EditIcon onClick={handleEditClick} />:
104
+ <PropertyValue value={currentValue} onClick={onClick} />
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  <PropertyEditDialog
106
  isOpen={isEditing}
107
  onClose={handleCancel}
lightrag_webui/src/components/graph/PropertiesView.tsx CHANGED
@@ -202,7 +202,6 @@ const PropertyRow = ({
202
  name={name}
203
  value={value}
204
  onClick={onClick}
205
- tooltip={tooltip}
206
  entityId={entityId}
207
  entityType={entityType}
208
  sourceId={sourceId}
 
202
  name={name}
203
  value={value}
204
  onClick={onClick}
 
205
  entityId={entityId}
206
  entityType={entityType}
207
  sourceId={sourceId}
lightrag_webui/src/components/graph/PropertyRowComponents.tsx ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { PencilIcon } from 'lucide-react'
2
+ import Text from '@/components/ui/Text'
3
+ import { useTranslation } from 'react-i18next'
4
+
5
+ interface PropertyNameProps {
6
+ name: string
7
+ }
8
+
9
+ export const PropertyName = ({ name }: PropertyNameProps) => {
10
+ const { t } = useTranslation()
11
+
12
+ const getPropertyNameTranslation = (propName: string) => {
13
+ const translationKey = `graphPanel.propertiesView.node.propertyNames.${propName}`
14
+ const translation = t(translationKey)
15
+ return translation === translationKey ? propName : translation
16
+ }
17
+
18
+ return (
19
+ <span className="text-primary/60 tracking-wide whitespace-nowrap">
20
+ {getPropertyNameTranslation(name)}
21
+ </span>
22
+ )
23
+ }
24
+
25
+ interface EditIconProps {
26
+ onClick: () => void
27
+ }
28
+
29
+ export const EditIcon = ({ onClick }: EditIconProps) => (
30
+ <div>
31
+ <PencilIcon
32
+ className="h-3 w-3 text-gray-500 hover:text-gray-700 cursor-pointer"
33
+ onClick={onClick}
34
+ />
35
+ </div>
36
+ )
37
+
38
+ interface PropertyValueProps {
39
+ value: any
40
+ onClick?: () => void
41
+ }
42
+
43
+ export const PropertyValue = ({ value, onClick }: PropertyValueProps) => (
44
+ <div className="flex items-center gap-1">
45
+ <Text
46
+ className="hover:bg-primary/20 rounded p-1 overflow-hidden text-ellipsis"
47
+ tooltipClassName="max-w-80"
48
+ text={value}
49
+ side="left"
50
+ onClick={onClick}
51
+ />
52
+ </div>
53
+ )
lightrag_webui/src/utils/graphOperations.ts ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, 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(String(nodeId))) {
27
+ return
28
+ }
29
+
30
+ try {
31
+ const nodeAttributes = sigmaGraph.getNodeAttributes(String(nodeId))
32
+
33
+ // Special handling for entity_id changes (node renaming)
34
+ if (propertyName === 'entity_id') {
35
+ // Create new node with updated ID but same attributes
36
+ sigmaGraph.addNode(newValue, { ...nodeAttributes, label: newValue })
37
+
38
+ const edgesToUpdate: EdgeToUpdate[] = []
39
+
40
+ // Process all edges connected to this node
41
+ sigmaGraph.forEachEdge(String(nodeId), (edge, attributes, source, target) => {
42
+ const otherNode = source === String(nodeId) ? target : source
43
+ const isOutgoing = source === String(nodeId)
44
+
45
+ // Get original edge dynamic ID for later reference
46
+ const originalEdgeDynamicId = edge
47
+ const edgeIndexInRawGraph = rawGraph.edgeDynamicIdMap[originalEdgeDynamicId]
48
+
49
+ // Create new edge with updated node reference
50
+ const newEdgeId = sigmaGraph.addEdge(
51
+ isOutgoing ? newValue : otherNode,
52
+ isOutgoing ? otherNode : newValue,
53
+ attributes
54
+ )
55
+
56
+ // Track edges that need updating in the raw graph
57
+ if (edgeIndexInRawGraph !== undefined) {
58
+ edgesToUpdate.push({
59
+ originalDynamicId: originalEdgeDynamicId,
60
+ newEdgeId: newEdgeId,
61
+ edgeIndex: edgeIndexInRawGraph
62
+ })
63
+ }
64
+
65
+ // Remove the old edge
66
+ sigmaGraph.dropEdge(edge)
67
+ })
68
+
69
+ // Remove the old node after all edges are processed
70
+ sigmaGraph.dropNode(String(nodeId))
71
+
72
+ // Update node reference in raw graph data
73
+ const nodeIndex = rawGraph.nodeIdMap[String(nodeId)]
74
+ if (nodeIndex !== undefined) {
75
+ rawGraph.nodes[nodeIndex].id = newValue
76
+ rawGraph.nodes[nodeIndex].properties.entity_id = newValue
77
+ delete rawGraph.nodeIdMap[String(nodeId)]
78
+ rawGraph.nodeIdMap[newValue] = nodeIndex
79
+ }
80
+
81
+ // Update all edge references in raw graph data
82
+ edgesToUpdate.forEach(({ originalDynamicId, newEdgeId, edgeIndex }) => {
83
+ if (rawGraph.edges[edgeIndex]) {
84
+ // Update source/target references
85
+ if (rawGraph.edges[edgeIndex].source === String(nodeId)) {
86
+ rawGraph.edges[edgeIndex].source = newValue
87
+ }
88
+ if (rawGraph.edges[edgeIndex].target === String(nodeId)) {
89
+ rawGraph.edges[edgeIndex].target = newValue
90
+ }
91
+
92
+ // Update dynamic ID mappings
93
+ rawGraph.edges[edgeIndex].dynamicId = newEdgeId
94
+ delete rawGraph.edgeDynamicIdMap[originalDynamicId]
95
+ rawGraph.edgeDynamicIdMap[newEdgeId] = edgeIndex
96
+ }
97
+ })
98
+
99
+ // Update selected node in store
100
+ useGraphStore.getState().setSelectedNode(newValue)
101
+ } else {
102
+ // For other properties, just update the property in raw graph
103
+ const nodeIndex = rawGraph.nodeIdMap[String(nodeId)]
104
+ if (nodeIndex !== undefined) {
105
+ rawGraph.nodes[nodeIndex].properties[propertyName] = newValue
106
+ }
107
+ }
108
+ } catch (error) {
109
+ console.error('Error updating node in graph:', error)
110
+ throw new Error('Failed to update node in graph')
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Update edge in the graph visualization
116
+ *
117
+ * @param sourceId - ID of the source node
118
+ * @param targetId - ID of the target node
119
+ * @param propertyName - Name of the property being updated
120
+ * @param newValue - New value for the property
121
+ */
122
+ export const updateGraphEdge = async (sourceId: string, targetId: string, propertyName: string, newValue: string) => {
123
+ // Get graph state from store
124
+ const sigmaGraph = useGraphStore.getState().sigmaGraph
125
+ const rawGraph = useGraphStore.getState().rawGraph
126
+
127
+ // Validate graph state
128
+ if (!sigmaGraph || !rawGraph) {
129
+ return
130
+ }
131
+
132
+ try {
133
+ // Find the edge between source and target nodes
134
+ const allEdges = sigmaGraph.edges()
135
+ let keyToUse = null
136
+
137
+ for (const edge of allEdges) {
138
+ const edgeSource = sigmaGraph.source(edge)
139
+ const edgeTarget = sigmaGraph.target(edge)
140
+
141
+ // Match edge in either direction (undirected graph support)
142
+ if ((edgeSource === sourceId && edgeTarget === targetId) ||
143
+ (edgeSource === targetId && edgeTarget === sourceId)) {
144
+ keyToUse = edge
145
+ break
146
+ }
147
+ }
148
+
149
+ if (keyToUse !== null) {
150
+ // Special handling for keywords property (updates edge label)
151
+ if(propertyName === 'keywords') {
152
+ sigmaGraph.setEdgeAttribute(keyToUse, 'label', newValue)
153
+ } else {
154
+ sigmaGraph.setEdgeAttribute(keyToUse, propertyName, newValue)
155
+ }
156
+
157
+ // Update edge in raw graph data using dynamic ID mapping
158
+ if (keyToUse && rawGraph.edgeDynamicIdMap[keyToUse] !== undefined) {
159
+ const edgeIndex = rawGraph.edgeDynamicIdMap[keyToUse]
160
+ if (rawGraph.edges[edgeIndex]) {
161
+ rawGraph.edges[edgeIndex].properties[propertyName] = newValue
162
+ }
163
+ } else if (keyToUse !== null) {
164
+ // Fallback: try to find edge by key in edge ID map
165
+ const edgeIndexByKey = rawGraph.edgeIdMap[keyToUse]
166
+ if (edgeIndexByKey !== undefined && rawGraph.edges[edgeIndexByKey]) {
167
+ rawGraph.edges[edgeIndexByKey].properties[propertyName] = newValue
168
+ }
169
+ }
170
+ }
171
+ } catch (error) {
172
+ console.error(`Error updating edge ${sourceId}->${targetId} in graph:`, error)
173
+ throw new Error('Failed to update edge in graph')
174
+ }
175
+ }