| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  | import { useState, useEffect, useRef } from 'react' | 
					
						
							|  |  |  | import { useTranslation } from 'react-i18next' | 
					
						
							|  |  |  | import Text from '@/components/ui/Text' | 
					
						
							|  |  |  | import Input from '@/components/ui/Input' | 
					
						
							|  |  |  | import { toast } from 'sonner' | 
					
						
							|  |  |  | import { updateEntity, updateRelation, checkEntityNameExists } from '@/api/lightrag' | 
					
						
							|  |  |  | import { useGraphStore } from '@/stores/graph' | 
					
						
							| 
									
										
										
										
											2025-04-12 14:33:40 +08:00
										 |  |  | import { PencilIcon } from 'lucide-react' | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  | import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/Tooltip' | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Interface for the EditablePropertyRow component props | 
					
						
							|  |  |  |  * Defines all possible properties that can be passed to the component | 
					
						
							|  |  |  |  */ | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  | interface EditablePropertyRowProps { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   name: string                  // Property name to display and edit
 | 
					
						
							|  |  |  |   value: any                    // Initial value of the property
 | 
					
						
							|  |  |  |   onClick?: () => void          // Optional click handler for the property value
 | 
					
						
							|  |  |  |   tooltip?: string              // Optional tooltip text
 | 
					
						
							|  |  |  |   entityId?: string             // ID of the entity (for node type)
 | 
					
						
							|  |  |  |   entityType?: 'node' | 'edge'  // Type of graph entity
 | 
					
						
							|  |  |  |   sourceId?: string            // Source node ID (for edge type)
 | 
					
						
							|  |  |  |   targetId?: string            // Target node ID (for edge type)
 | 
					
						
							|  |  |  |   onValueChange?: (newValue: any) => void  // Optional callback when value changes
 | 
					
						
							|  |  |  |   isEditable?: boolean         // Whether this property can be edited
 | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Interface for tracking edges that need updating when a node ID changes | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | interface EdgeToUpdate { | 
					
						
							|  |  |  |   originalDynamicId: string; | 
					
						
							|  |  |  |   newEdgeId: string; | 
					
						
							|  |  |  |   edgeIndex: number; | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * EditablePropertyRow component that supports double-click to edit property values | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |  * This component is used in the graph properties panel to display and edit entity properties | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * @component | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |  */ | 
					
						
							|  |  |  | const EditablePropertyRow = ({ | 
					
						
							|  |  |  |   name, | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |   value: initialValue, | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |   onClick, | 
					
						
							|  |  |  |   tooltip, | 
					
						
							|  |  |  |   entityId, | 
					
						
							|  |  |  |   entityType, | 
					
						
							|  |  |  |   sourceId, | 
					
						
							|  |  |  |   targetId, | 
					
						
							|  |  |  |   onValueChange, | 
					
						
							|  |  |  |   isEditable = false | 
					
						
							|  |  |  | }: EditablePropertyRowProps) => { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   // Component state
 | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |   const { t } = useTranslation() | 
					
						
							|  |  |  |   const [isEditing, setIsEditing] = useState(false) | 
					
						
							|  |  |  |   const [editValue, setEditValue] = useState('') | 
					
						
							|  |  |  |   const [isSubmitting, setIsSubmitting] = useState(false) | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |   const [currentValue, setCurrentValue] = useState(initialValue) | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |   const inputRef = useRef<HTMLInputElement>(null) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Update currentValue when initialValue changes from parent | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     setCurrentValue(initialValue) | 
					
						
							|  |  |  |   }, [initialValue]) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Initialize edit value and focus input when entering edit mode | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |   useEffect(() => { | 
					
						
							|  |  |  |     if (isEditing) { | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |       setEditValue(String(currentValue)) | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |       // Focus the input element when entering edit mode with a small delay
 | 
					
						
							|  |  |  |       // to ensure the input is rendered before focusing
 | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |       setTimeout(() => { | 
					
						
							|  |  |  |         if (inputRef.current) { | 
					
						
							|  |  |  |           inputRef.current.focus() | 
					
						
							|  |  |  |           inputRef.current.select() | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }, 50) | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |   }, [isEditing, currentValue]) | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Get translated property name from i18n | 
					
						
							|  |  |  |    * Falls back to the original name if no translation is found | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |   const getPropertyNameTranslation = (propName: string) => { | 
					
						
							|  |  |  |     const translationKey = `graphPanel.propertiesView.node.propertyNames.${propName}` | 
					
						
							|  |  |  |     const translation = t(translationKey) | 
					
						
							|  |  |  |     return translation === translationKey ? propName : translation | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Handle double-click event to enter edit mode | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |   const handleDoubleClick = () => { | 
					
						
							|  |  |  |     if (isEditable && !isEditing) { | 
					
						
							|  |  |  |       setIsEditing(true) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Handle keyboard events in the input field | 
					
						
							|  |  |  |    * - Enter: Save changes | 
					
						
							|  |  |  |    * - Escape: Cancel editing | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |   const handleKeyDown = (e: React.KeyboardEvent) => { | 
					
						
							|  |  |  |     if (e.key === 'Enter') { | 
					
						
							|  |  |  |       handleSave() | 
					
						
							|  |  |  |     } else if (e.key === 'Escape') { | 
					
						
							|  |  |  |       setIsEditing(false) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Update node in the graph visualization after API update | 
					
						
							|  |  |  |    * Handles both property updates and entity ID changes | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @param nodeId - ID of the node to update | 
					
						
							|  |  |  |    * @param propertyName - Name of the property being updated | 
					
						
							|  |  |  |    * @param newValue - New value for the property | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |   const updateGraphNode = async (nodeId: string, propertyName: string, newValue: string) => { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |     // Get graph state from store
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |     const sigmaInstance = useGraphStore.getState().sigmaInstance | 
					
						
							|  |  |  |     const sigmaGraph = useGraphStore.getState().sigmaGraph | 
					
						
							|  |  |  |     const rawGraph = useGraphStore.getState().rawGraph | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |     // Validate graph state
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |     if (!sigmaInstance || !sigmaGraph || !rawGraph || !sigmaGraph.hasNode(String(nodeId))) { | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |       return | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |       const nodeAttributes = sigmaGraph.getNodeAttributes(String(nodeId)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |       // Special handling for entity_id changes (node renaming)
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |       if (propertyName === 'entity_id') { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Create new node with updated ID but same attributes
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |         sigmaGraph.addNode(newValue, { ...nodeAttributes, label: newValue }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         const edgesToUpdate: EdgeToUpdate[] = []; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Process all edges connected to this node
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |         sigmaGraph.forEachEdge(String(nodeId), (edge, attributes, source, target) => { | 
					
						
							|  |  |  |           const otherNode = source === String(nodeId) ? target : source | 
					
						
							|  |  |  |           const isOutgoing = source === String(nodeId) | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |           // Get original edge dynamic ID for later reference
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |           const originalEdgeDynamicId = edge | 
					
						
							|  |  |  |           const edgeIndexInRawGraph = rawGraph.edgeDynamicIdMap[originalEdgeDynamicId] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |           // Create new edge with updated node reference
 | 
					
						
							|  |  |  |           const newEdgeId = sigmaGraph.addEdge( | 
					
						
							|  |  |  |             isOutgoing ? newValue : otherNode, | 
					
						
							|  |  |  |             isOutgoing ? otherNode : newValue, | 
					
						
							|  |  |  |             attributes | 
					
						
							|  |  |  |           ) | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |           // Track edges that need updating in the raw graph
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |           if (edgeIndexInRawGraph !== undefined) { | 
					
						
							|  |  |  |             edgesToUpdate.push({ | 
					
						
							|  |  |  |               originalDynamicId: originalEdgeDynamicId, | 
					
						
							|  |  |  |               newEdgeId: newEdgeId, | 
					
						
							|  |  |  |               edgeIndex: edgeIndexInRawGraph | 
					
						
							|  |  |  |             }) | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |           // Remove the old edge
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |           sigmaGraph.dropEdge(edge) | 
					
						
							|  |  |  |         }) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Remove the old node after all edges are processed
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |         sigmaGraph.dropNode(String(nodeId)) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Update node reference in raw graph data
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |         const nodeIndex = rawGraph.nodeIdMap[String(nodeId)] | 
					
						
							|  |  |  |         if (nodeIndex !== undefined) { | 
					
						
							|  |  |  |           rawGraph.nodes[nodeIndex].id = newValue | 
					
						
							|  |  |  |           rawGraph.nodes[nodeIndex].properties.entity_id = newValue | 
					
						
							|  |  |  |           delete rawGraph.nodeIdMap[String(nodeId)] | 
					
						
							|  |  |  |           rawGraph.nodeIdMap[newValue] = nodeIndex | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Update all edge references in raw graph data
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         edgesToUpdate.forEach(({ originalDynamicId, newEdgeId, edgeIndex }) => { | 
					
						
							|  |  |  |           if (rawGraph.edges[edgeIndex]) { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |             // Update source/target references
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |             if (rawGraph.edges[edgeIndex].source === String(nodeId)) { | 
					
						
							|  |  |  |               rawGraph.edges[edgeIndex].source = newValue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (rawGraph.edges[edgeIndex].target === String(nodeId)) { | 
					
						
							|  |  |  |               rawGraph.edges[edgeIndex].target = newValue | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |             // Update dynamic ID mappings
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |             rawGraph.edges[edgeIndex].dynamicId = newEdgeId | 
					
						
							|  |  |  |             delete rawGraph.edgeDynamicIdMap[originalDynamicId] | 
					
						
							|  |  |  |             rawGraph.edgeDynamicIdMap[newEdgeId] = edgeIndex | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |         }) | 
					
						
							| 
									
										
										
										
											2025-04-12 10:36:05 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Update selected node in store
 | 
					
						
							|  |  |  |         useGraphStore.getState().setSelectedNode(newValue) | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |       } else { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // For other properties, just update the property in raw graph
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |         const nodeIndex = rawGraph.nodeIdMap[String(nodeId)] | 
					
						
							|  |  |  |         if (nodeIndex !== undefined) { | 
					
						
							|  |  |  |           rawGraph.nodes[nodeIndex].properties[propertyName] = newValue | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |     } catch (error) { | 
					
						
							|  |  |  |       console.error('Error updating node in graph:', error) | 
					
						
							|  |  |  |       throw new Error('Failed to update node in graph') | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-04-12 10:36:05 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Update edge in the graph visualization after API update | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * @param sourceId - ID of the source node | 
					
						
							|  |  |  |    * @param targetId - ID of the target node | 
					
						
							|  |  |  |    * @param propertyName - Name of the property being updated | 
					
						
							|  |  |  |    * @param newValue - New value for the property | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |   const updateGraphEdge = async (sourceId: string, targetId: string, propertyName: string, newValue: string) => { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |     // Get graph state from store
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |     const sigmaInstance = useGraphStore.getState().sigmaInstance | 
					
						
							|  |  |  |     const sigmaGraph = useGraphStore.getState().sigmaGraph | 
					
						
							|  |  |  |     const rawGraph = useGraphStore.getState().rawGraph | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |     // Validate graph state
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |     if (!sigmaInstance || !sigmaGraph || !rawGraph) { | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-04-12 10:36:05 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |     try { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |       // Find the edge between source and target nodes
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |       const allEdges = sigmaGraph.edges() | 
					
						
							|  |  |  |       let keyToUse = null | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       for (const edge of allEdges) { | 
					
						
							|  |  |  |         const edgeSource = sigmaGraph.source(edge) | 
					
						
							|  |  |  |         const edgeTarget = sigmaGraph.target(edge) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Match edge in either direction (undirected graph support)
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         if ((edgeSource === sourceId && edgeTarget === targetId) || | 
					
						
							|  |  |  |             (edgeSource === targetId && edgeTarget === sourceId)) { | 
					
						
							|  |  |  |           keyToUse = edge | 
					
						
							|  |  |  |           break | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-04-12 10:36:05 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |       if (keyToUse !== null) { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Special handling for keywords property (updates edge label)
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         if(propertyName === 'keywords') { | 
					
						
							|  |  |  |           sigmaGraph.setEdgeAttribute(keyToUse, 'label', newValue); | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           sigmaGraph.setEdgeAttribute(keyToUse, propertyName, newValue); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Update edge in raw graph data using dynamic ID mapping
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         if (keyToUse && rawGraph.edgeDynamicIdMap[keyToUse] !== undefined) { | 
					
						
							|  |  |  |            const edgeIndex = rawGraph.edgeDynamicIdMap[keyToUse]; | 
					
						
							|  |  |  |            if (rawGraph.edges[edgeIndex]) { | 
					
						
							|  |  |  |                rawGraph.edges[edgeIndex].properties[propertyName] = newValue; | 
					
						
							|  |  |  |            } else { | 
					
						
							|  |  |  |                console.warn(`Edge index ${edgeIndex} found but edge data missing in rawGraph for dynamicId ${entityId}`); | 
					
						
							|  |  |  |            } | 
					
						
							|  |  |  |         } else { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |           // Fallback: try to find edge by key in edge ID map
 | 
					
						
							|  |  |  |           console.warn(`Could not find edge with dynamicId ${entityId} in rawGraph.edgeDynamicIdMap to update properties.`); | 
					
						
							|  |  |  |           if (keyToUse !== null) { | 
					
						
							|  |  |  |             const edgeIndexByKey = rawGraph.edgeIdMap[keyToUse]; | 
					
						
							|  |  |  |             if (edgeIndexByKey !== undefined && rawGraph.edges[edgeIndexByKey]) { | 
					
						
							|  |  |  |                 rawGraph.edges[edgeIndexByKey].properties[propertyName] = newValue; | 
					
						
							|  |  |  |                 console.log(`Updated rawGraph edge using constructed key ${keyToUse}`); | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 console.warn(`Could not find edge in rawGraph using key ${keyToUse} either.`); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |           } else { | 
					
						
							|  |  |  |             console.warn('Cannot update edge properties: edge key is null'); | 
					
						
							|  |  |  |           } | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         } | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         console.warn(`Edge not found in sigmaGraph with key ${keyToUse}`); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |     } catch (error) { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |       // Log the specific edge key that caused the error
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |       console.error(`Error updating edge ${sourceId}->${targetId} in graph:`, error); | 
					
						
							|  |  |  |       throw new Error('Failed to update edge in graph') | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2025-04-12 10:36:05 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Save changes to the property value | 
					
						
							|  |  |  |    * Updates both the API and the graph visualization | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |   const handleSave = async () => { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |     // Prevent duplicate submissions
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |     if (isSubmitting) return | 
					
						
							| 
									
										
										
										
											2025-04-12 10:36:05 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |     // Skip if value hasn't changed
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |     if (editValue === String(currentValue)) { | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |       setIsEditing(false) | 
					
						
							|  |  |  |       return | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |     setIsSubmitting(true) | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |     try { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |       // Handle node property updates
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |       if (entityType === 'node' && entityId) { | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         let updatedData = { [name]: editValue } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Special handling for entity_id (name) changes
 | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |         if (name === 'entity_id') { | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |           // Check if the new name already exists
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |           const exists = await checkEntityNameExists(editValue) | 
					
						
							|  |  |  |           if (exists) { | 
					
						
							|  |  |  |             toast.error(t('graphPanel.propertiesView.errors.duplicateName')) | 
					
						
							|  |  |  |             setIsSubmitting(false) | 
					
						
							|  |  |  |             return | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |           // For entity_id, we update entity_name in the API
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |           updatedData = { 'entity_name': editValue } | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |         } | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |         // Update entity in API
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         await updateEntity(entityId, updatedData, true) | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Update graph visualization
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         await updateGraphNode(entityId, name, editValue) | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |         toast.success(t('graphPanel.propertiesView.success.entityUpdated')) | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |       } | 
					
						
							|  |  |  |       // Handle edge property updates
 | 
					
						
							|  |  |  |       else if (entityType === 'edge' && sourceId && targetId) { | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         const updatedData = { [name]: editValue } | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Update relation in API
 | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |         await updateRelation(sourceId, targetId, updatedData) | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Update graph visualization
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         await updateGraphEdge(sourceId, targetId, name, editValue) | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |         toast.success(t('graphPanel.propertiesView.success.relationUpdated')) | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |       // Update local state
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |       setIsEditing(false) | 
					
						
							|  |  |  |       setCurrentValue(editValue) | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |       // Notify parent component if callback provided
 | 
					
						
							|  |  |  |       if (onValueChange) { | 
					
						
							|  |  |  |         onValueChange(editValue) | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |     } catch (error) { | 
					
						
							| 
									
										
										
										
											2025-04-12 13:17:09 +08:00
										 |  |  |       console.error('Error updating property:', error) | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |       toast.error(t('graphPanel.propertiesView.errors.updateFailed')) | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |     } finally { | 
					
						
							|  |  |  |       setIsSubmitting(false) | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Render the property row with edit functionality | 
					
						
							|  |  |  |    * Shows property name, edit icon, and either the editable input or the current value | 
					
						
							|  |  |  |    */ | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |   return ( | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |     <div className="flex items-center gap-1" onDoubleClick={handleDoubleClick}> | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |       {/* Property name with translation */} | 
					
						
							|  |  |  |       <span className="text-primary/60 tracking-wide whitespace-nowrap"> | 
					
						
							|  |  |  |         {getPropertyNameTranslation(name)} | 
					
						
							|  |  |  |       </span> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       {/* Edit icon with tooltip */} | 
					
						
							|  |  |  |       <TooltipProvider delayDuration={200}> | 
					
						
							|  |  |  |         <Tooltip> | 
					
						
							|  |  |  |           <TooltipTrigger asChild> | 
					
						
							|  |  |  |             <div> | 
					
						
							|  |  |  |               <PencilIcon | 
					
						
							|  |  |  |                 className="h-3 w-3 text-gray-500 hover:text-gray-700 cursor-pointer" | 
					
						
							|  |  |  |                 onClick={() => setIsEditing(true)} | 
					
						
							|  |  |  |               /> | 
					
						
							|  |  |  |             </div> | 
					
						
							|  |  |  |           </TooltipTrigger> | 
					
						
							|  |  |  |           <TooltipContent side="top"> | 
					
						
							|  |  |  |             {t('graphPanel.propertiesView.doubleClickToEdit')} | 
					
						
							|  |  |  |           </TooltipContent> | 
					
						
							|  |  |  |         </Tooltip> | 
					
						
							|  |  |  |       </TooltipProvider>: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       {/* Conditional rendering based on edit state */} | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |       {isEditing ? ( | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Input field for editing
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         <Input | 
					
						
							|  |  |  |           ref={inputRef} | 
					
						
							|  |  |  |           type="text" | 
					
						
							|  |  |  |           value={editValue} | 
					
						
							|  |  |  |           onChange={(e) => setEditValue(e.target.value)} | 
					
						
							|  |  |  |           onKeyDown={handleKeyDown} | 
					
						
							|  |  |  |           onBlur={handleSave} | 
					
						
							|  |  |  |           className="h-6 text-xs" | 
					
						
							|  |  |  |           disabled={isSubmitting} | 
					
						
							|  |  |  |         /> | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |       ) : ( | 
					
						
							| 
									
										
										
										
											2025-04-13 11:13:23 +08:00
										 |  |  |         // Text display when not editing
 | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |         <div className="flex items-center gap-1"> | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |           <Text | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |             className="hover:bg-primary/20 rounded p-1 overflow-hidden text-ellipsis" | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |             tooltipClassName="max-w-80" | 
					
						
							| 
									
										
										
										
											2025-04-12 15:09:44 +08:00
										 |  |  |             text={currentValue} | 
					
						
							|  |  |  |             tooltip={tooltip || (typeof currentValue === 'string' ? currentValue : JSON.stringify(currentValue, null, 2))} | 
					
						
							| 
									
										
										
										
											2025-04-12 00:48:19 +08:00
										 |  |  |             side="left" | 
					
						
							|  |  |  |             onClick={onClick} | 
					
						
							|  |  |  |           /> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       )} | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export default EditablePropertyRow |