diff --git a/core/web_api.py b/core/web_api.py index eba3cb8..7922a35 100644 --- a/core/web_api.py +++ b/core/web_api.py @@ -808,7 +808,8 @@ def get_graph(): # 查询关系(边) cursor.execute(""" - SELECT r.id, r.source_id, r.target_id, r.relation_type, r.confidence, r.status + SELECT r.id, r.source_id, r.target_id, r.relation_type, + r.confidence, r.status, r.created_at, r.updated_at FROM relations r WHERE r.status = 'active' """) @@ -821,7 +822,9 @@ def get_graph(): "target": row['target_id'], "relation_type": row['relation_type'], "confidence": row['confidence'], - "status": row['status'] + "status": row['status'], + "created_at": row['created_at'], + "updated_at": row['updated_at'] }) return jsonify({ diff --git a/ui/static/graph.html b/ui/static/graph.html index 4caac28..037941a 100644 --- a/ui/static/graph.html +++ b/ui/static/graph.html @@ -548,6 +548,7 @@ let currentHighlightIds = []; let currentHighlightEdgeIds = new Set(); // 存储高亮的边ID let edgeParticles = {}; // 存储边的流动光点 {edgeId: {mesh, progress, edge}} + let edgeLabels = []; // 存储边标签 // 颜色映射 const typeColors = { @@ -812,11 +813,13 @@ function init() { // 清除旧的对象(包括边粒子) nodeMeshes.forEach(mesh => scene.remove(mesh)); edgeLines.forEach(line => scene.remove(line)); + edgeLabels.forEach(label => scene.remove(label)); Object.keys(edgeParticles).forEach(key => { scene.remove(edgeParticles[key].mesh); }); nodeMeshes = []; edgeLines = []; + edgeLabels = []; edgeParticles = {}; if (nodes.length === 0) return; @@ -1081,6 +1084,53 @@ function init() { scene.add(line); edgeLines.push(line); + + // 边标签(中间位置显示关系类型 + 时间戳) + const midX = (pos1.x + pos2.x) / 2; + const midY = (pos1.y + pos2.y) / 2; + const midZ = (pos1.z + pos2.z) / 2; + + const labelCanvas = document.createElement('canvas'); + const labelCtx = labelCanvas.getContext('2d'); + labelCanvas.width = 256; + labelCanvas.height = 48; + labelCtx.clearRect(0, 0, labelCanvas.width, labelCanvas.height); + + // 时间戳文本 + const ts = edge.created_at || ''; + const tsShort = ts ? ts.substring(0, 10) : ''; + let labelText = edge.relation_type; + if (tsShort) labelText += '\n' + tsShort; + + labelCtx.font = '16px Courier New'; + labelCtx.fillStyle = color; + labelCtx.textAlign = 'center'; + labelCtx.shadowColor = color; + labelCtx.shadowBlur = 4; + labelCtx.fillText(edge.relation_type, 128, 20); + if (tsShort) { + labelCtx.font = '12px Courier New'; + labelCtx.fillStyle = '#999999'; + labelCtx.fillText(tsShort, 128, 38); + } + + const labelTex = new THREE.CanvasTexture(labelCanvas); + labelTex.needsUpdate = true; + const labelMat = new THREE.SpriteMaterial({ + map: labelTex, + transparent: true, + opacity: 0.7, + depthTest: false, + depthWrite: false, + blending: THREE.AdditiveBlending + }); + const edgeLabel = new THREE.Sprite(labelMat); + edgeLabel.scale.set(5, 1, 1); + edgeLabel.position.set(midX, midY, midZ); + edgeLabel.userData = { edgeId: edge.id }; + + scene.add(edgeLabel); + edgeLabels.push(edgeLabel); }); // 调整相机位置以适应所有节点