mirror of
https://github.com/HKUDS/LightRAG.git
synced 2025-08-02 13:51:56 +00:00
261 lines
8.5 KiB
TypeScript
261 lines
8.5 KiB
TypeScript
import { useRegisterEvents, useSetSettings, useSigma } from '@react-sigma/core'
|
|
import { AbstractGraph } from 'graphology-types'
|
|
// import { useLayoutCircular } from '@react-sigma/layout-circular'
|
|
import { useLayoutForceAtlas2 } from '@react-sigma/layout-forceatlas2'
|
|
import { useEffect } from 'react'
|
|
|
|
// import useRandomGraph, { EdgeType, NodeType } from '@/hooks/useRandomGraph'
|
|
import { EdgeType, NodeType } from '@/hooks/useLightragGraph'
|
|
import useTheme from '@/hooks/useTheme'
|
|
import * as Constants from '@/lib/constants'
|
|
|
|
import { useSettingsStore } from '@/stores/settings'
|
|
import { useGraphStore } from '@/stores/graph'
|
|
|
|
const isButtonPressed = (ev: MouseEvent | TouchEvent) => {
|
|
if (ev.type.startsWith('mouse')) {
|
|
if ((ev as MouseEvent).buttons !== 0) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
const GraphControl = ({ disableHoverEffect }: { disableHoverEffect?: boolean }) => {
|
|
const sigma = useSigma<NodeType, EdgeType>()
|
|
const registerEvents = useRegisterEvents<NodeType, EdgeType>()
|
|
const setSettings = useSetSettings<NodeType, EdgeType>()
|
|
|
|
const maxIterations = useSettingsStore.use.graphLayoutMaxIterations()
|
|
const { assign: assignLayout } = useLayoutForceAtlas2({
|
|
iterations: maxIterations
|
|
})
|
|
|
|
const { theme } = useTheme()
|
|
const hideUnselectedEdges = useSettingsStore.use.enableHideUnselectedEdges()
|
|
const enableEdgeEvents = useSettingsStore.use.enableEdgeEvents()
|
|
const renderEdgeLabels = useSettingsStore.use.showEdgeLabel()
|
|
const renderLabels = useSettingsStore.use.showNodeLabel()
|
|
const selectedNode = useGraphStore.use.selectedNode()
|
|
const focusedNode = useGraphStore.use.focusedNode()
|
|
const selectedEdge = useGraphStore.use.selectedEdge()
|
|
const focusedEdge = useGraphStore.use.focusedEdge()
|
|
const sigmaGraph = useGraphStore.use.sigmaGraph()
|
|
|
|
/**
|
|
* When component mount or maxIterations changes
|
|
* => ensure graph reference and apply layout
|
|
*/
|
|
useEffect(() => {
|
|
if (sigmaGraph && sigma) {
|
|
// 确保 sigma 实例内部的 graph 引用被更新
|
|
try {
|
|
// 尝试直接设置 sigma 实例的 graph 引用
|
|
if (typeof sigma.setGraph === 'function') {
|
|
sigma.setGraph(sigmaGraph as unknown as AbstractGraph<NodeType, EdgeType>);
|
|
console.log('Directly set graph on sigma instance');
|
|
} else {
|
|
// 如果 setGraph 方法不存在,尝试直接设置 graph 属性
|
|
(sigma as any).graph = sigmaGraph;
|
|
console.log('Set graph property on sigma instance');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error setting graph on sigma instance:', error);
|
|
}
|
|
|
|
// 应用布局
|
|
assignLayout();
|
|
console.log('Layout applied to graph');
|
|
}
|
|
}, [sigma, sigmaGraph, assignLayout, maxIterations])
|
|
|
|
/**
|
|
* Ensure the sigma instance is set in the store
|
|
* This provides a backup in case the instance wasn't set in GraphViewer
|
|
*/
|
|
useEffect(() => {
|
|
if (sigma) {
|
|
// Double-check that the store has the sigma instance
|
|
const currentInstance = useGraphStore.getState().sigmaInstance;
|
|
if (!currentInstance) {
|
|
console.log('Setting sigma instance from GraphControl (backup)');
|
|
useGraphStore.getState().setSigmaInstance(sigma);
|
|
}
|
|
}
|
|
}, [sigma]);
|
|
|
|
/**
|
|
* When component mount
|
|
* => register events
|
|
*/
|
|
useEffect(() => {
|
|
const { setFocusedNode, setSelectedNode, setFocusedEdge, setSelectedEdge, clearSelection } =
|
|
useGraphStore.getState()
|
|
|
|
// Define event types
|
|
type NodeEvent = { node: string; event: { original: MouseEvent | TouchEvent } }
|
|
type EdgeEvent = { edge: string; event: { original: MouseEvent | TouchEvent } }
|
|
|
|
// Register all events, but edge events will only be processed if enableEdgeEvents is true
|
|
const events: Record<string, any> = {
|
|
enterNode: (event: NodeEvent) => {
|
|
if (!isButtonPressed(event.event.original)) {
|
|
setFocusedNode(event.node)
|
|
}
|
|
},
|
|
leaveNode: (event: NodeEvent) => {
|
|
if (!isButtonPressed(event.event.original)) {
|
|
setFocusedNode(null)
|
|
}
|
|
},
|
|
clickNode: (event: NodeEvent) => {
|
|
setSelectedNode(event.node)
|
|
setSelectedEdge(null)
|
|
},
|
|
clickStage: () => clearSelection()
|
|
}
|
|
|
|
// Only add edge event handlers if enableEdgeEvents is true
|
|
if (enableEdgeEvents) {
|
|
events.clickEdge = (event: EdgeEvent) => {
|
|
setSelectedEdge(event.edge)
|
|
setSelectedNode(null)
|
|
}
|
|
|
|
events.enterEdge = (event: EdgeEvent) => {
|
|
if (!isButtonPressed(event.event.original)) {
|
|
setFocusedEdge(event.edge)
|
|
}
|
|
}
|
|
|
|
events.leaveEdge = (event: EdgeEvent) => {
|
|
if (!isButtonPressed(event.event.original)) {
|
|
setFocusedEdge(null)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Register the events
|
|
registerEvents(events)
|
|
}, [registerEvents, enableEdgeEvents])
|
|
|
|
/**
|
|
* When component mount or hovered node change
|
|
* => Setting the sigma reducers
|
|
*/
|
|
useEffect(() => {
|
|
const isDarkTheme = theme === 'dark'
|
|
const labelColor = isDarkTheme ? Constants.labelColorDarkTheme : undefined
|
|
const edgeColor = isDarkTheme ? Constants.edgeColorDarkTheme : undefined
|
|
|
|
// Update all dynamic settings directly without recreating the sigma container
|
|
setSettings({
|
|
// Update display settings
|
|
enableEdgeEvents,
|
|
renderEdgeLabels,
|
|
renderLabels,
|
|
|
|
// Node reducer for node appearance
|
|
nodeReducer: (node, data) => {
|
|
const graph = sigma.getGraph()
|
|
const newData: NodeType & {
|
|
labelColor?: string
|
|
borderColor?: string
|
|
} = { ...data, highlighted: data.highlighted || false, labelColor }
|
|
|
|
if (!disableHoverEffect) {
|
|
newData.highlighted = false
|
|
const _focusedNode = focusedNode || selectedNode
|
|
const _focusedEdge = focusedEdge || selectedEdge
|
|
|
|
if (_focusedNode && graph.hasNode(_focusedNode)) {
|
|
try {
|
|
if (node === _focusedNode || graph.neighbors(_focusedNode).includes(node)) {
|
|
newData.highlighted = true
|
|
if (node === selectedNode) {
|
|
newData.borderColor = Constants.nodeBorderColorSelected
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in nodeReducer:', error);
|
|
}
|
|
} else if (_focusedEdge && graph.hasEdge(_focusedEdge)) {
|
|
if (graph.extremities(_focusedEdge).includes(node)) {
|
|
newData.highlighted = true
|
|
newData.size = 3
|
|
}
|
|
} else {
|
|
return newData
|
|
}
|
|
|
|
if (newData.highlighted) {
|
|
if (isDarkTheme) {
|
|
newData.labelColor = Constants.LabelColorHighlightedDarkTheme
|
|
}
|
|
} else {
|
|
newData.color = Constants.nodeColorDisabled
|
|
}
|
|
}
|
|
return newData
|
|
},
|
|
|
|
// Edge reducer for edge appearance
|
|
edgeReducer: (edge, data) => {
|
|
const graph = sigma.getGraph()
|
|
const newData = { ...data, hidden: false, labelColor, color: edgeColor }
|
|
|
|
if (!disableHoverEffect) {
|
|
const _focusedNode = focusedNode || selectedNode
|
|
|
|
if (_focusedNode && graph.hasNode(_focusedNode)) {
|
|
try {
|
|
if (hideUnselectedEdges) {
|
|
if (!graph.extremities(edge).includes(_focusedNode)) {
|
|
newData.hidden = true
|
|
}
|
|
} else {
|
|
if (graph.extremities(edge).includes(_focusedNode)) {
|
|
newData.color = Constants.edgeColorHighlighted
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error('Error in edgeReducer:', error);
|
|
}
|
|
} else {
|
|
const _selectedEdge = selectedEdge && graph.hasEdge(selectedEdge) ? selectedEdge : null;
|
|
const _focusedEdge = focusedEdge && graph.hasEdge(focusedEdge) ? focusedEdge : null;
|
|
|
|
if (_selectedEdge || _focusedEdge) {
|
|
if (edge === _selectedEdge) {
|
|
newData.color = Constants.edgeColorSelected
|
|
} else if (edge === _focusedEdge) {
|
|
newData.color = Constants.edgeColorHighlighted
|
|
} else if (hideUnselectedEdges) {
|
|
newData.hidden = true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return newData
|
|
}
|
|
})
|
|
}, [
|
|
selectedNode,
|
|
focusedNode,
|
|
selectedEdge,
|
|
focusedEdge,
|
|
setSettings,
|
|
sigma,
|
|
disableHoverEffect,
|
|
theme,
|
|
hideUnselectedEdges,
|
|
enableEdgeEvents,
|
|
renderEdgeLabels,
|
|
renderLabels
|
|
])
|
|
|
|
return null
|
|
}
|
|
|
|
export default GraphControl
|