From f2c471c7ba9fd2921f40bcd28c980b1b12d9f9a9 Mon Sep 17 00:00:00 2001 From: yangdx Date: Tue, 15 Apr 2025 12:27:03 +0800 Subject: [PATCH] Fix Neo4j node and edge edit problem --- .../components/graph/EditablePropertyRow.tsx | 14 +++- .../src/components/graph/PropertiesView.tsx | 25 ++++-- lightrag_webui/src/stores/graph.ts | 9 ++- lightrag_webui/src/utils/graphOperations.ts | 76 +++++++------------ 4 files changed, 64 insertions(+), 60 deletions(-) diff --git a/lightrag_webui/src/components/graph/EditablePropertyRow.tsx b/lightrag_webui/src/components/graph/EditablePropertyRow.tsx index a1d0c23c..f5cfe64f 100644 --- a/lightrag_webui/src/components/graph/EditablePropertyRow.tsx +++ b/lightrag_webui/src/components/graph/EditablePropertyRow.tsx @@ -13,7 +13,10 @@ interface EditablePropertyRowProps { name: string // Property name to display and edit value: any // Initial value of the property onClick?: () => void // Optional click handler for the property value + nodeId?: string // ID of the node (for node type) entityId?: string // ID of the entity (for node type) + edgeId?: string // ID of the edge (for edge type) + dynamicId?: string entityType?: 'node' | 'edge' // Type of graph entity sourceId?: string // Source node ID (for edge type) targetId?: string // Target node ID (for edge type) @@ -30,7 +33,10 @@ const EditablePropertyRow = ({ name, value: initialValue, onClick, + nodeId, + edgeId, entityId, + dynamicId, entityType, sourceId, targetId, @@ -66,7 +72,7 @@ const EditablePropertyRow = ({ setIsSubmitting(true) try { - if (entityType === 'node' && entityId) { + if (entityType === 'node' && entityId && nodeId) { let updatedData = { [name]: value } if (name === 'entity_id') { @@ -79,12 +85,12 @@ const EditablePropertyRow = ({ } await updateEntity(entityId, updatedData, true) - await updateGraphNode(entityId, name, value) + await updateGraphNode(nodeId, entityId, name, value) toast.success(t('graphPanel.propertiesView.success.entityUpdated')) - } else if (entityType === 'edge' && sourceId && targetId) { + } else if (entityType === 'edge' && sourceId && targetId && edgeId && dynamicId) { const updatedData = { [name]: value } await updateRelation(sourceId, targetId, updatedData) - await updateGraphEdge(sourceId, targetId, name, value) + await updateGraphEdge(edgeId, dynamicId, sourceId, targetId, name, value) toast.success(t('graphPanel.propertiesView.success.relationUpdated')) } diff --git a/lightrag_webui/src/components/graph/PropertiesView.tsx b/lightrag_webui/src/components/graph/PropertiesView.tsx index b63f1b09..39bd3bb4 100644 --- a/lightrag_webui/src/components/graph/PropertiesView.tsx +++ b/lightrag_webui/src/components/graph/PropertiesView.tsx @@ -93,6 +93,7 @@ const refineNodeProperties = (node: RawNodeType): NodeType => { if (state.sigmaGraph && state.rawGraph) { try { if (!state.sigmaGraph.hasNode(node.id)) { + console.warn('Node not found in sigmaGraph:', node.id) return { ...node, relationships: [] @@ -139,7 +140,8 @@ const refineEdgeProperties = (edge: RawEdgeType): EdgeType => { if (state.sigmaGraph && state.rawGraph) { try { - if (!state.sigmaGraph.hasEdge(edge.id)) { + if (!state.sigmaGraph.hasEdge(edge.dynamicId)) { + console.warn('Edge not found in sigmaGraph:', edge.id, 'dynamicId:', edge.dynamicId) return { ...edge, sourceNode: undefined, @@ -171,6 +173,9 @@ const PropertyRow = ({ value, onClick, tooltip, + nodeId, + edgeId, + dynamicId, entityId, entityType, sourceId, @@ -181,7 +186,10 @@ const PropertyRow = ({ value: any onClick?: () => void tooltip?: string + nodeId?: string entityId?: string + edgeId?: string + dynamicId?: string entityType?: 'node' | 'edge' sourceId?: string targetId?: string @@ -202,7 +210,10 @@ const PropertyRow = ({ name={name} value={value} onClick={onClick} + nodeId={nodeId} entityId={entityId} + edgeId={edgeId} + dynamicId={dynamicId} entityType={entityType} sourceId={sourceId} targetId={targetId} @@ -265,7 +276,7 @@ const NodePropertiesView = ({ node }: { node: NodeType }) => {
- + { key={name} name={name} value={node.properties[name]} - entityId={node.properties['entity_id'] || node.id} + nodeId={String(node.id)} + entityId={node.properties['entity_id']} entityType="node" isEditable={name === 'description' || name === 'entity_id'} /> @@ -350,10 +362,11 @@ const EdgePropertiesView = ({ edge }: { edge: EdgeType }) => { key={name} name={name} value={edge.properties[name]} - entityId={edge.id} + edgeId={String(edge.id)} + dynamicId={String(edge.dynamicId)} entityType="edge" - sourceId={edge.source} - targetId={edge.target} + sourceId={edge.sourceNode?.properties['entity_id'] || edge.source} + targetId={edge.targetNode?.properties['entity_id'] || edge.target} isEditable={name === 'description' || name === 'keywords'} /> ) diff --git a/lightrag_webui/src/stores/graph.ts b/lightrag_webui/src/stores/graph.ts index 84000170..37a48545 100644 --- a/lightrag_webui/src/stores/graph.ts +++ b/lightrag_webui/src/stores/graph.ts @@ -5,6 +5,8 @@ import { getGraphLabels } from '@/api/lightrag' import MiniSearch from 'minisearch' export type RawNodeType = { + // for NetworkX: id is identical to properties['entity_id'] + // for Neo4j: id is unique identifier for each node id: string labels: string[] properties: Record @@ -18,20 +20,25 @@ export type RawNodeType = { } export type RawEdgeType = { + // for NetworkX: id is "source-target" + // for Neo4j: id is unique identifier for each edge id: string source: string target: string type?: string properties: Record - + // dynamicId: key for sigmaGraph dynamicId: string } export class RawGraph { nodes: RawNodeType[] = [] edges: RawEdgeType[] = [] + // nodeIDMap: map node id to index in nodes array (SigmaGraph has nodeId as key) nodeIdMap: Record = {} + // edgeIDMap: map edge id to index in edges array (SigmaGraph not use id as key) edgeIdMap: Record = {} + // edgeDynamicIdMap: map edge dynamic id to index in edges array (SigmaGraph has DynamicId as key) edgeDynamicIdMap: Record = {} getNode = (nodeId: string) => { diff --git a/lightrag_webui/src/utils/graphOperations.ts b/lightrag_webui/src/utils/graphOperations.ts index c99ac17d..d36b312f 100644 --- a/lightrag_webui/src/utils/graphOperations.ts +++ b/lightrag_webui/src/utils/graphOperations.ts @@ -17,30 +17,32 @@ interface EdgeToUpdate { * @param propertyName - Name of the property being updated * @param newValue - New value for the property */ -export const updateGraphNode = async (nodeId: string, propertyName: string, newValue: string) => { +export const updateGraphNode = async (nodeId: string, entityId:string, propertyName: string, newValue: string) => { // Get graph state from store const sigmaGraph = useGraphStore.getState().sigmaGraph const rawGraph = useGraphStore.getState().rawGraph // Validate graph state - if (!sigmaGraph || !rawGraph || !sigmaGraph.hasNode(String(nodeId))) { + if (!sigmaGraph || !rawGraph || !sigmaGraph.hasNode(nodeId)) { return } try { - const nodeAttributes = sigmaGraph.getNodeAttributes(String(nodeId)) + const nodeAttributes = sigmaGraph.getNodeAttributes(nodeId) - // Special handling for entity_id changes (node renaming) - if (propertyName === 'entity_id') { + console.log('updateGraphNode', nodeId, entityId, propertyName, newValue) + + // For entity_id changes (node renaming) with NetworkX graph storage + if ((nodeId === entityId) && (propertyName === 'entity_id')) { // Create new node with updated ID but same attributes sigmaGraph.addNode(newValue, { ...nodeAttributes, label: newValue }) const edgesToUpdate: EdgeToUpdate[] = [] // Process all edges connected to this node - sigmaGraph.forEachEdge(String(nodeId), (edge, attributes, source, target) => { - const otherNode = source === String(nodeId) ? target : source - const isOutgoing = source === String(nodeId) + sigmaGraph.forEachEdge(nodeId, (edge, attributes, source, target) => { + const otherNode = source === nodeId ? target : source + const isOutgoing = source === nodeId // Get original edge dynamic ID for later reference const originalEdgeDynamicId = edge @@ -67,14 +69,15 @@ export const updateGraphNode = async (nodeId: string, propertyName: string, newV }) // Remove the old node after all edges are processed - sigmaGraph.dropNode(String(nodeId)) + sigmaGraph.dropNode(nodeId) // Update node reference in raw graph data - const nodeIndex = rawGraph.nodeIdMap[String(nodeId)] + const nodeIndex = rawGraph.nodeIdMap[nodeId] if (nodeIndex !== undefined) { rawGraph.nodes[nodeIndex].id = newValue + rawGraph.nodes[nodeIndex].labels = [newValue] rawGraph.nodes[nodeIndex].properties.entity_id = newValue - delete rawGraph.nodeIdMap[String(nodeId)] + delete rawGraph.nodeIdMap[nodeId] rawGraph.nodeIdMap[newValue] = nodeIndex } @@ -82,10 +85,10 @@ export const updateGraphNode = async (nodeId: string, propertyName: string, newV edgesToUpdate.forEach(({ originalDynamicId, newEdgeId, edgeIndex }) => { if (rawGraph.edges[edgeIndex]) { // Update source/target references - if (rawGraph.edges[edgeIndex].source === String(nodeId)) { + if (rawGraph.edges[edgeIndex].source === nodeId) { rawGraph.edges[edgeIndex].source = newValue } - if (rawGraph.edges[edgeIndex].target === String(nodeId)) { + if (rawGraph.edges[edgeIndex].target === nodeId) { rawGraph.edges[edgeIndex].target = newValue } @@ -99,10 +102,14 @@ export const updateGraphNode = async (nodeId: string, propertyName: string, newV // Update selected node in store useGraphStore.getState().setSelectedNode(newValue) } else { - // For other properties, just update the property in raw graph + // for none NetworkX nodes or none entity_id changes const nodeIndex = rawGraph.nodeIdMap[String(nodeId)] if (nodeIndex !== undefined) { rawGraph.nodes[nodeIndex].properties[propertyName] = newValue + if (propertyName === 'entity_id') { + rawGraph.nodes[nodeIndex].labels = [newValue] + sigmaGraph.setNodeAttribute(String(nodeId), 'label', newValue) + } } } } catch (error) { @@ -119,7 +126,7 @@ export const updateGraphNode = async (nodeId: string, propertyName: string, newV * @param propertyName - Name of the property being updated * @param newValue - New value for the property */ -export const updateGraphEdge = async (sourceId: string, targetId: string, propertyName: string, newValue: string) => { +export const updateGraphEdge = async (edgeId: string, dynamicId: string, sourceId: string, targetId: string, propertyName: string, newValue: string) => { // Get graph state from store const sigmaGraph = useGraphStore.getState().sigmaGraph const rawGraph = useGraphStore.getState().rawGraph @@ -130,44 +137,15 @@ export const updateGraphEdge = async (sourceId: string, targetId: string, proper } try { - // Find the edge between source and target nodes - const allEdges = sigmaGraph.edges() - let keyToUse = null - for (const edge of allEdges) { - const edgeSource = sigmaGraph.source(edge) - const edgeTarget = sigmaGraph.target(edge) - - // Match edge in either direction (undirected graph support) - if ((edgeSource === sourceId && edgeTarget === targetId) || - (edgeSource === targetId && edgeTarget === sourceId)) { - keyToUse = edge - break + const edgeIndex = rawGraph.edgeIdMap[String(edgeId)] + if (edgeIndex !== undefined && rawGraph.edges[edgeIndex]) { + rawGraph.edges[edgeIndex].properties[propertyName] = newValue + if(dynamicId !== undefined && propertyName === 'keywords') { + sigmaGraph.setEdgeAttribute(dynamicId, 'label', newValue) } } - if (keyToUse !== null) { - // Special handling for keywords property (updates edge label) - if(propertyName === 'keywords') { - sigmaGraph.setEdgeAttribute(keyToUse, 'label', newValue) - } else { - sigmaGraph.setEdgeAttribute(keyToUse, propertyName, newValue) - } - - // Update edge in raw graph data using dynamic ID mapping - if (keyToUse && rawGraph.edgeDynamicIdMap[keyToUse] !== undefined) { - const edgeIndex = rawGraph.edgeDynamicIdMap[keyToUse] - if (rawGraph.edges[edgeIndex]) { - rawGraph.edges[edgeIndex].properties[propertyName] = newValue - } - } else if (keyToUse !== null) { - // Fallback: try to find edge by key in edge ID map - const edgeIndexByKey = rawGraph.edgeIdMap[keyToUse] - if (edgeIndexByKey !== undefined && rawGraph.edges[edgeIndexByKey]) { - rawGraph.edges[edgeIndexByKey].properties[propertyName] = newValue - } - } - } } catch (error) { console.error(`Error updating edge ${sourceId}->${targetId} in graph:`, error) throw new Error('Failed to update edge in graph')