ForcePilot/web/src/stores/graphStore.js
2026-03-26 13:53:35 +08:00

435 lines
13 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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] = 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
}
}
}
})