import React, { type FC, useEffect, useState, useRef, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom' import { Col } from 'antd' import RbCard from '@/components/RbCard/Card' import ReactEcharts from 'echarts-for-react' import zoom from '@/assets/images/userMemory/zoom.svg' import drag from '@/assets/images/userMemory/drag.svg' import pointer from '@/assets/images/userMemory/pointer.svg' import empty from '@/assets/images/userMemory/empty.svg' import type { Node, Edge, GraphData } from '../types' import { getMemorySearchEdges, } from '@/api/memory' import Empty from '@/components/Empty' import dayjs from 'dayjs' const operations = [ { name: 'click', icon: pointer }, { name: 'drag', icon: drag }, { name: 'zoom', icon: zoom }, ] const RelationshipNetwork:FC = () => { const { t } = useTranslation() const { id } = useParams() const chartRef = useRef(null) const resizeScheduledRef = useRef(false) const [nodes, setNodes] = useState([]) const [links, setLinks] = useState([]) const [categories, setCategories] = useState<{ name: string }[]>([]) const [selectedNode, setSelectedNode] = useState(null) // 关系网络 const getEdgeData = useCallback(() => { if (!id) return setSelectedNode(null) getMemorySearchEdges(id).then((res) => { const { nodes, edges, statistics } = res as GraphData const curNodes: Node[] = [] const curEdges: Edge[] = [] const curNodeTypes = Object.keys(statistics.node_types) // 计算每个节点的连接数 const connectionCount: Record = {} edges.forEach(edge => { connectionCount[edge.source] = (connectionCount[edge.source] || 0) + 1 connectionCount[edge.target] = (connectionCount[edge.target] || 0) + 1 }) // 处理节点数据 nodes.forEach(node => { const connections = connectionCount[node.id] || 0 const categoryIndex = curNodeTypes.indexOf(node.label) // 根据节点类型获取显示名称 let displayName = '' switch (node.label) { case 'Statement': displayName = 'statement' in node.properties ? node.properties.statement?.slice(0, 5) || '' : '' break case 'ExtractedEntity': displayName = 'name' in node.properties ? node.properties.name || '' : '' break default: displayName = 'content' in node.properties ? node.properties.content?.slice(0, 5) || '' : '' break } let symbolSize = 0 if (connections <= 1) { symbolSize = 5 } else if (connections <= 10) { symbolSize = 10 } else if (connections <= 15) { symbolSize = 15 } else if (connections <= 20) { symbolSize = 25 } else { symbolSize = 35 } curNodes.push({ ...node, name: displayName, category: categoryIndex >= 0 ? categoryIndex : 0, symbolSize: symbolSize, // 根据连接数调整节点大小 itemStyle: { color: ['#155EEF', '#4DA8FF', '#9C6FFF', '#8BAEF7', '#369F21', '#FF5D34', '#FF8A4C', '#FFB048'][categoryIndex % 8] } }) }) // 处理边数据 edges.forEach(edge => { curEdges.push({ ...edge, source: edge.source, target: edge.target, value: edge.weight || 1 }) }) // 设置分类 const curCategories = curNodeTypes.map(type => ({ name: type })) setNodes(curNodes) setLinks(curEdges) setCategories(curCategories) }) }, [id]) useEffect(() => { if (!id) return getEdgeData() }, [id]) useEffect(() => { const handleResize = () => { if (chartRef.current && !resizeScheduledRef.current) { resizeScheduledRef.current = true requestAnimationFrame(() => { chartRef.current?.getEchartsInstance().resize(); resizeScheduledRef.current = false }); } } const resizeObserver = new ResizeObserver(handleResize) const chartElement = chartRef.current?.getEchartsInstance().getDom().parentElement if (chartElement) { resizeObserver.observe(chartElement) } return () => { resizeObserver.disconnect() } }, [nodes]) return ( <> {/* 关系网络 */}
{nodes.length === 0 ? ( ) : ( { if (params.dataType === 'node') { // 处理节点点击事件 console.log('Node clicked:', params.data); // 使用函数式更新避免状态依赖问题 setSelectedNode(params.data) } } }} /> )}
{operations.map((item) => (
{t(`userMemory.${item.name}`)}
))}
{/* 记忆详情 */} {!selectedNode ? : <>
{t('userMemory.memoryContent')}
{['Chunk', 'Dialogue', 'MemorySummary'].includes(selectedNode.label) && 'content' in selectedNode.properties ? selectedNode.properties.content : selectedNode.label === 'ExtractedEntity' && 'description' in selectedNode.properties ? selectedNode.properties.description : selectedNode.label === 'Statement' && 'statement' in selectedNode.properties ? selectedNode.properties.statement : '' }
{t('userMemory.created_at')}
{dayjs(selectedNode?.properties.created_at).format('YYYY/MM/DD HH:mm:ss')}
}
) } // 使用React.memo包装组件,避免不必要的渲染 export default React.memo(RelationshipNetwork)