From 4dcd27180d6f588effe822b532f1bc692f84a7b8 Mon Sep 17 00:00:00 2001 From: Bifang <915779419@qq.com> Date: Mon, 15 Jun 2026 17:50:47 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E5=9B=BE=E8=B0=B1=E6=B8=B2?= =?UTF-8?q?=E6=9F=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- meeting_memory/web_demo/static/graph.js | 52 ++++++++++++++++++++ meeting_memory/web_demo/static_v2/styles.css | 30 +++++++++++ 2 files changed, 82 insertions(+) diff --git a/meeting_memory/web_demo/static/graph.js b/meeting_memory/web_demo/static/graph.js index 4f037de..1f0dc86 100644 --- a/meeting_memory/web_demo/static/graph.js +++ b/meeting_memory/web_demo/static/graph.js @@ -143,6 +143,20 @@ function renderGraph(payload) { .map((e) => ({ ...e, sourceNode: nodeById.get(e.source), targetNode: nodeById.get(e.target) })) .filter((e) => e.sourceNode && e.targetNode); + // ── adjacency map for hover highlight ── + const nodeEdges = new Map(); // nodeId -> Set of edge indices + const nodeNeighbors = new Map(); // nodeId -> Set of neighbor nodeIds + dataNodes.forEach(n => { + nodeEdges.set(n.id, new Set()); + nodeNeighbors.set(n.id, new Set()); + }); + dataEdges.forEach((e, i) => { + nodeEdges.get(e.source).add(i); + nodeEdges.get(e.target).add(i); + nodeNeighbors.get(e.source).add(e.target); + nodeNeighbors.get(e.target).add(e.source); + }); + const n = dataNodes.length; const area = svgW * svgH; const repulsionStr = Math.max(3000, area / Math.max(n, 1)); @@ -203,9 +217,45 @@ function renderGraph(payload) { text.textContent = truncate(node.label, TRUNCATE_LENGTH); g.appendChild(text); mainGroup.appendChild(g); + + // ── hover highlight ── + g.addEventListener('mouseenter', () => applyFocus(node)); + g.addEventListener('mouseleave', () => { if (!isDragging) clearFocus(); }); + return g; }); + // ── focus / dim helpers ── + function applyFocus(hoveredNode) { + const connectedEdges = nodeEdges.get(hoveredNode.id); + const connectedNodes = nodeNeighbors.get(hoveredNode.id); + dataNodes.forEach((n, idx) => { + const el = nodeEls[idx]; + if (!el) return; + const isTarget = n.id === hoveredNode.id || (connectedNodes && connectedNodes.has(n.id)); + el.classList.toggle('graph-dim', !isTarget); + el.classList.toggle('graph-focus', isTarget); + }); + dataEdges.forEach((e, idx) => { + const el = edgeEls[idx]; + if (!el) return; + const isConnected = connectedEdges && connectedEdges.has(idx); + el.classList.toggle('graph-dim', !isConnected); + el.classList.toggle('graph-focus', isConnected); + }); + } + + function clearFocus() { + dataNodes.forEach((n, idx) => { + const el = nodeEls[idx]; + if (el) { el.classList.remove('graph-dim', 'graph-focus'); } + }); + dataEdges.forEach((e, idx) => { + const el = edgeEls[idx]; + if (el) { el.classList.remove('graph-dim', 'graph-focus'); } + }); + } + function syncDom() { for (let i = 0; i < dataEdges.length; i++) { const edge = dataEdges[i], el = edgeEls[i]; @@ -330,6 +380,7 @@ function renderGraph(payload) { dragOccurred = false; dragNode = dataNodes.find((n) => n.id === target.dataset.nodeId); if (dragNode) { + applyFocus(dragNode); const r = graphSvg.getBoundingClientRect(); dragOffX = (e.clientX - r.left - zoomTransform.x) / zoomTransform.k - dragNode.x; dragOffY = (e.clientY - r.top - zoomTransform.y) / zoomTransform.k - dragNode.y; @@ -367,6 +418,7 @@ function renderGraph(payload) { if (el) el.style.cursor = "grab"; dragNode.vx = 0; dragNode.vy = 0; + clearFocus(); wakeSim(); } isDragging = false; diff --git a/meeting_memory/web_demo/static_v2/styles.css b/meeting_memory/web_demo/static_v2/styles.css index 401ff6b..df925a1 100644 --- a/meeting_memory/web_demo/static_v2/styles.css +++ b/meeting_memory/web_demo/static_v2/styles.css @@ -862,6 +862,17 @@ textarea { box-shadow: var(--shadow-lg); } +.detail-modal { + border: none; + border-radius: 12px; + padding: 0; + width: min(640px, 90vw); + max-height: 80vh; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15); + inset: 0; + margin: auto; +} + .detail-modal::backdrop { background: rgba(30, 41, 59, 0.3); } @@ -1143,6 +1154,25 @@ textarea { .graph-node:hover circle { opacity: 0.8; } +/* ── hover / drag dim + focus ── */ +.graph-dim { opacity: 0.2 !important; transition: opacity 0.2s; } +.edge-wrap { transition: opacity 0.2s; } + +.graph-focus circle { + filter: drop-shadow(0 0 8px rgba(6, 182, 212, 0.5)); +} +.graph-focus text[data-type="node-label"] { + font-weight: 700; +} +.edge-wrap.graph-focus .graph-edge { + stroke: var(--gray-500); + stroke-width: 2.5; +} +.edge-wrap.graph-focus text[data-type="edge-label"] { + fill: var(--gray-700); + font-weight: 600; +} + .graph-node text { font-size: 11px; font-weight: 500;