choizhang commited on
Commit
217a79c
·
1 Parent(s): 39a1836

refactor(graph): Refactoring the EditablePeopleRow component

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