ForcePilot/web/src/stores/graphStore.js

424 lines
13 KiB
JavaScript
Raw Normal View History

import { defineStore } from 'pinia'
import { DirectedGraph } from 'graphology'
export const useGraphStore = defineStore('graph', {
state: () => ({
// 选中和聚焦状态
selectedNode: null,
focusedNode: null,
selectedEdge: null,
focusedEdge: null,
// 图数据
rawGraph: null,
sigmaGraph: null,
sigmaInstance: null,
// 实体类型和颜色映射
entityTypes: [],
typeColorMap: new Map(),
// 加载状态
isFetching: false,
graphIsEmpty: false,
// 移动到选中节点的标志
moveToSelectedNode: false,
// 图统计信息
stats: {
displayed_nodes: 0,
displayed_edges: 0,
is_truncated: false
}
}),
getters: {
// 获取选中节点的详细信息
selectedNodeData: (state) => {
if (!state.selectedNode || !state.rawGraph) return null
return state.rawGraph.nodes.find(node => node.id === state.selectedNode)
},
// 获取选中边的详细信息
selectedEdgeData: (state) => {
if (!state.selectedEdge || !state.rawGraph) return null
console.log('查找边数据选中边ID:', state.selectedEdge)
// 首先尝试通过dynamicId匹配Sigma使用的ID格式
let foundEdge = state.rawGraph.edges.find(edge => edge.dynamicId === state.selectedEdge)
if (foundEdge) {
console.log('通过dynamicId找到边:', foundEdge)
return foundEdge
}
// 如果没找到尝试通过原始ID匹配
foundEdge = state.rawGraph.edges.find(edge => edge.id === state.selectedEdge)
if (foundEdge) {
console.log('通过id找到边:', foundEdge)
return foundEdge
}
// 对于格式为 source-target-index 的dynamicId也尝试解析
const dynamicIdPattern = /^(.+)-(.+)-(\d+)$/
const match = state.selectedEdge.match(dynamicIdPattern)
if (match) {
const [, source, target, index] = match
foundEdge = state.rawGraph.edges.find(edge =>
edge.source === source && edge.target === target
)
if (foundEdge) {
console.log('通过解析dynamicId找到边:', foundEdge)
return foundEdge
}
}
// 最后尝试通过source->target格式匹配
const arrowPattern = /^(.+)->(.+)$/
const arrowMatch = state.selectedEdge.match(arrowPattern)
if (arrowMatch) {
const [, source, target] = arrowMatch
foundEdge = state.rawGraph.edges.find(edge =>
edge.source === source.trim() && edge.target === target.trim()
)
if (foundEdge) {
console.log('通过箭头格式找到边:', foundEdge)
return foundEdge
}
}
console.warn('未找到匹配的边数据选中边ID:', state.selectedEdge)
return null
},
// 检查图是否为空
isGraphEmpty: (state) => {
return !state.rawGraph || state.rawGraph.nodes.length === 0
}
},
actions: {
// 设置Sigma实例
setSigmaInstance(instance) {
this.sigmaInstance = instance
},
// 节点选择和聚焦
setSelectedNode(nodeId, moveToNode = false) {
this.selectedNode = nodeId
this.moveToSelectedNode = moveToNode
// 如果选中节点,清除选中的边
if (nodeId) {
this.selectedEdge = null
}
},
setFocusedNode(nodeId) {
this.focusedNode = nodeId
},
setSelectedEdge(edgeId) {
this.selectedEdge = edgeId
// 如果选中边,清除选中的节点
if (edgeId) {
this.selectedNode = null
}
},
setFocusedEdge(edgeId) {
this.focusedEdge = edgeId
},
// 清除所有选择
clearSelection() {
this.selectedNode = null
this.focusedNode = null
this.selectedEdge = null
this.focusedEdge = null
},
// 设置加载状态
setIsFetching(isFetching) {
this.isFetching = isFetching
},
// 设置实体类型
setEntityTypes(types) {
this.entityTypes = types
this.updateTypeColorMap()
},
// 更新类型颜色映射
updateTypeColorMap() {
const colorPalette = [
'#FF6B6B', // 人物 - 红色
'#4ECDC4', // 组织 - 青色
'#45B7D1', // 地点 - 蓝色
'#96CEB4', // 事件 - 绿色
'#FFEAA7', // 分类 - 黄色
'#DDA0DD', // 设备 - 紫色
'#FF7675', // 运动员 - 红色
'#FD79A8', // 记录 - 粉色
'#FDCB6E', // 年份 - 橙色
'#B2BEC3' // 未知 - 灰色
]
const typeColorMap = new Map()
this.entityTypes.forEach((type, index) => {
const colorIndex = index % colorPalette.length
typeColorMap.set(type.type, colorPalette[colorIndex])
})
// 为特殊类型设置固定颜色
typeColorMap.set('person', '#FF6B6B')
typeColorMap.set('organization', '#4ECDC4')
typeColorMap.set('location', '#45B7D1')
typeColorMap.set('geo', '#45B7D1')
typeColorMap.set('event', '#96CEB4')
typeColorMap.set('category', '#FFEAA7')
typeColorMap.set('unknown', '#B2BEC3')
this.typeColorMap = typeColorMap
},
// 获取实体类型颜色
getEntityColor(entityType) {
return this.typeColorMap.get(entityType) || '#B2BEC3'
},
// 设置原始图数据
setRawGraph(rawGraph) {
this.rawGraph = rawGraph
this.graphIsEmpty = !rawGraph || rawGraph.nodes.length === 0
this.updateStats()
},
// 设置Sigma图数据
setSigmaGraph(sigmaGraph) {
this.sigmaGraph = sigmaGraph
},
// 更新统计信息
updateStats() {
if (this.rawGraph) {
this.stats = {
displayed_nodes: this.rawGraph.nodes.length,
displayed_edges: this.rawGraph.edges.length,
is_truncated: this.rawGraph.is_truncated ?? this.stats?.is_truncated ?? false
}
}
},
// 从API数据创建图数据结构
createGraphFromApiData(nodesData, edgesData) {
const rawGraph = {
nodes: [],
edges: [],
nodeIdMap: {},
edgeIdMap: {},
edgeDynamicIdMap: {}
}
console.log('Processing nodes data:', nodesData)
// 处理节点数据
nodesData.forEach((node, index) => {
// 适配新的LightRAG API格式
const nodeId = String(node.id)
const labels = node.labels || [node.entity_type || 'unknown']
const entityType = node.entity_type || labels[0] || 'unknown'
const processedNode = {
id: nodeId,
labels: Array.isArray(labels) ? labels.map(String) : [String(labels)],
entity_type: String(entityType),
properties: {
entity_id: String(node.properties?.entity_id || node.entity_id || nodeId),
entity_type: String(entityType),
description: String(node.properties?.description || node.description || ''),
file_path: String(node.properties?.file_path || node.file_path || ''),
...(node.properties || {})
},
// Sigma.js需要的属性
size: this.calculateNodeSize(node),
x: Math.random() * 1000, // 随机初始位置
y: Math.random() * 1000,
color: this.getEntityColor(String(entityType)),
degree: 0 // 将在处理边时计算
}
rawGraph.nodes.push(processedNode)
rawGraph.nodeIdMap[nodeId] = index
})
// 计算节点度数
const nodeDegrees = {}
edgesData.forEach(edge => {
const sourceId = String(edge.source)
const targetId = String(edge.target)
nodeDegrees[sourceId] = (nodeDegrees[sourceId] || 0) + 1
nodeDegrees[targetId] = (nodeDegrees[targetId] || 0) + 1
})
// 更新节点度数和大小
rawGraph.nodes.forEach(node => {
node.degree = nodeDegrees[node.id] || 0
node.size = this.calculateNodeSize({ degree: node.degree })
})
console.log('Processing edges data:', edgesData)
// 处理边数据
edgesData.forEach((edge, index) => {
const sourceId = String(edge.source)
const targetId = String(edge.target)
const dynamicId = `${sourceId}-${targetId}-${index}`
// 适配新的LightRAG API格式
const weight = Number(edge.properties?.weight || edge.weight || 1.0)
const processedEdge = {
id: String(edge.id),
source: sourceId,
target: targetId,
type: edge.type || 'DIRECTED',
properties: {
weight: weight,
keywords: String(edge.properties?.keywords || edge.keywords || ''),
description: String(edge.properties?.description || edge.description || ''),
file_path: String(edge.properties?.file_path || edge.file_path || ''),
...(edge.properties || {})
},
dynamicId: dynamicId,
// Sigma.js需要的属性
size: this.calculateEdgeSize(weight),
color: '#666',
originalWeight: weight
}
rawGraph.edges.push(processedEdge)
rawGraph.edgeIdMap[edge.id] = index
rawGraph.edgeDynamicIdMap[dynamicId] = index
})
return rawGraph
},
// 计算节点大小
calculateNodeSize(node) {
const baseSizeM = 15
const degree = node.degree || 0
return Math.min(baseSizeM + degree * 3, 40)
},
// 计算边大小
calculateEdgeSize(weight) {
const minSize = 3 // 与Sigma配置中的minEdgeThickness保持一致
const maxSize = 8 // 与Sigma配置中的maxEdgeThickness保持一致
const normalizedWeight = Math.max(0, Math.min(1, (weight - 1) / 9)) // 假设权重范围1-10
return minSize + normalizedWeight * (maxSize - minSize)
},
// 创建Sigma图实例
createSigmaGraph(rawGraph) {
console.log('开始创建Sigma图节点数量:', rawGraph.nodes.length, '边数量:', rawGraph.edges.length)
const sigmaGraph = new DirectedGraph()
// 添加节点
rawGraph.nodes.forEach(node => {
// 确保所有属性都是正确的类型
const nodeAttributes = {
label: String(node.properties?.entity_id || node.id),
size: Number(node.size) || 15,
color: String(node.color) || '#B2BEC3',
x: Number(node.x) || Math.random() * 1000,
y: Number(node.y) || Math.random() * 1000,
// 保存原始数据引用
originalData: node
}
sigmaGraph.addNode(String(node.id), nodeAttributes)
})
console.log('节点添加完成,开始添加边...')
// 添加边
let edgeAddedCount = 0
let edgeSkippedCount = 0
rawGraph.edges.forEach((edge, index) => {
// 添加调试信息
if (index < 3) {
console.log('处理边 #' + index + ':', {
id: edge.id,
source: edge.source,
target: edge.target,
dynamicId: edge.dynamicId,
edgeObject: edge
})
}
if (sigmaGraph.hasNode(String(edge.source)) && sigmaGraph.hasNode(String(edge.target))) {
// 确保所有属性都是正确的类型
const edgeAttributes = {
size: Number(edge.size) || 1,
color: String(edge.color) || '#666',
label: String(edge.properties?.keywords || edge.properties?.description || ''),
originalWeight: Number(edge.originalWeight) || 1,
// 保存原始数据引用
originalData: edge
}
// 使用标准的 addEdge 方法addEdge(edgeId, source, target, attributes)
try {
// 使用动态ID作为Sigma边ID避免重复
const sigmaEdgeId = edge.dynamicId || `${edge.source}->${edge.target}`
// 检查是否已存在相同的边
if (!sigmaGraph.hasEdge(sigmaEdgeId)) {
sigmaGraph.addEdgeWithKey(sigmaEdgeId, String(edge.source), String(edge.target), edgeAttributes)
edgeAddedCount++
} else {
edgeSkippedCount++
}
} catch (err) {
console.warn('添加边失败:', {
source: edge.source,
target: edge.target,
attributes: edgeAttributes,
error: err.message
})
}
} else {
console.warn('节点不存在,跳过边:', {
source: edge.source,
target: edge.target,
hasSource: sigmaGraph.hasNode(String(edge.source)),
hasTarget: sigmaGraph.hasNode(String(edge.target))
})
}
})
console.log(`边添加完成: 成功 ${edgeAddedCount}, 跳过 ${edgeSkippedCount}`)
return sigmaGraph
},
// 重置所有状态
reset() {
this.selectedNode = null
this.focusedNode = null
this.selectedEdge = null
this.focusedEdge = null
this.rawGraph = null
this.sigmaGraph = null
this.moveToSelectedNode = false
this.graphIsEmpty = false
this.stats = {
displayed_nodes: 0,
displayed_edges: 0,
is_truncated: false
}
}
}
})