Phase 5: Add Neo4j graph analysis frontend panel

- Add graph analysis sidebar button and panel UI in workbench.html
- Implement graph statistics display (nodes, edges, density, components)
- Implement centrality analysis ranking display
- Implement community detection visualization using D3.js force layout
- Implement shortest path query and visualization
- Implement neighbor node query and visualization
- Add Neo4j connection status indicator
- Add data sync to Neo4j functionality
- Update STATUS.md marking frontend graph panel as completed
- Style consistent with dark theme using #00d4ff as primary color
This commit is contained in:
OpenClaw Bot
2026-02-20 18:06:42 +08:00
parent 6521d4b45f
commit 0286e96909
3 changed files with 1633 additions and 9 deletions

View File

@@ -146,9 +146,16 @@ Phase 5: 高级功能 - **进行中 🚧**
- `GET /api/v1/projects/{id}/graph/communities` - 社区发现
- `POST /api/v1/graph/subgraph` - 子图提取
- [x] 部署 Neo4j 服务 (docker-compose)
- [ ] 前端图分析面板
- [ ] 路径可视化
- [ ] 社区可视化
- [x] 前端图分析面板
- 图统计信息展示(节点数、边数、密度、连通分量)
- 度中心性排名展示
- 社区发现可视化D3.js 力导向图)
- 最短路径查询和可视化
- 邻居节点查询和可视化
- Neo4j 连接状态指示
- 数据同步按钮
- [ ] 路径可视化优化
- [ ] 社区可视化增强
### Phase 4 - Neo4j 集成 (可选)
- [ ] 将图谱数据同步到 Neo4j

File diff suppressed because it is too large Load Diff

View File

@@ -1471,6 +1471,460 @@
color: #00d4ff;
text-align: center;
}
/* Phase 5: Graph Analysis Panel */
.graph-analysis-panel {
display: none;
flex-direction: column;
width: 100%;
height: 100%;
background: #0a0a0a;
}
.graph-analysis-panel.active {
display: flex;
}
.graph-analysis-header {
padding: 16px 20px;
background: #141414;
border-bottom: 1px solid #222;
display: flex;
justify-content: space-between;
align-items: center;
}
.graph-analysis-header h2 {
font-size: 1.3rem;
margin-bottom: 4px;
}
.graph-analysis-actions {
display: flex;
gap: 10px;
}
.graph-analysis-content {
flex: 1;
display: flex;
overflow: hidden;
}
.graph-analysis-sidebar {
width: 320px;
background: #111;
border-right: 1px solid #222;
padding: 16px;
overflow-y: auto;
}
.graph-analysis-section {
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #222;
}
.graph-analysis-section:last-child {
border-bottom: none;
}
.graph-analysis-section h4 {
color: #00d4ff;
font-size: 0.9rem;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.graph-stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.graph-stat-item {
background: #141414;
border: 1px solid #222;
border-radius: 8px;
padding: 12px;
text-align: center;
}
.graph-stat-value {
display: block;
font-size: 1.5rem;
font-weight: 600;
color: #00d4ff;
margin-bottom: 4px;
}
.graph-stat-label {
font-size: 0.75rem;
color: #666;
}
.graph-query-group {
background: #141414;
border: 1px solid #222;
border-radius: 8px;
padding: 12px;
margin-bottom: 12px;
}
.graph-query-group label {
display: block;
color: #888;
font-size: 0.8rem;
margin-bottom: 8px;
}
.graph-query-select,
.graph-query-input {
width: 100%;
background: #1a1a1a;
border: 1px solid #333;
border-radius: 6px;
padding: 8px 12px;
color: #e0e0e0;
font-size: 0.85rem;
margin-bottom: 8px;
}
.graph-query-select:focus,
.graph-query-input:focus {
outline: none;
border-color: #00d4ff;
}
.graph-query-options {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.graph-query-options label {
margin-bottom: 0;
}
.graph-query-input {
width: 60px;
margin-bottom: 0;
}
.graph-analysis-tabs {
display: flex;
gap: 8px;
}
.graph-analysis-tab {
flex: 1;
padding: 10px;
background: #141414;
border: 1px solid #222;
border-radius: 6px;
color: #888;
cursor: pointer;
font-size: 0.85rem;
transition: all 0.2s;
}
.graph-analysis-tab:hover {
border-color: #00d4ff;
color: #00d4ff;
}
.graph-analysis-tab.active {
background: #00d4ff22;
border-color: #00d4ff;
color: #00d4ff;
}
.graph-analysis-viz {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
.graph-viz-panel {
display: none;
flex-direction: column;
height: 100%;
overflow: hidden;
}
.graph-viz-panel.active {
display: flex;
}
.graph-viz-header {
padding: 16px 20px;
background: #141414;
border-bottom: 1px solid #222;
}
.graph-viz-header h3 {
font-size: 1.1rem;
margin-bottom: 4px;
}
.centrality-list {
flex: 1;
overflow-y: auto;
padding: 16px;
}
.centrality-item {
display: flex;
align-items: center;
padding: 12px 16px;
background: #141414;
border: 1px solid #222;
border-radius: 8px;
margin-bottom: 8px;
cursor: pointer;
transition: all 0.2s;
}
.centrality-item:hover {
border-color: #00d4ff;
background: #1a1a1a;
}
.centrality-rank {
width: 32px;
height: 32px;
background: #00d4ff22;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #00d4ff;
font-weight: 600;
font-size: 0.9rem;
margin-right: 12px;
}
.centrality-rank.top3 {
background: #00d4ff;
color: #000;
}
.centrality-info {
flex: 1;
}
.centrality-name {
font-weight: 500;
color: #e0e0e0;
margin-bottom: 2px;
}
.centrality-type {
font-size: 0.75rem;
color: #666;
}
.centrality-score {
text-align: right;
}
.centrality-value {
font-size: 1.2rem;
font-weight: 600;
color: #00d4ff;
}
.centrality-label {
font-size: 0.75rem;
color: #666;
}
.communities-viz {
flex: 1;
background: #0a0a0a;
position: relative;
overflow: hidden;
}
.communities-viz svg {
width: 100%;
height: 100%;
}
.communities-list {
max-height: 200px;
overflow-y: auto;
padding: 12px 16px;
background: #111;
border-top: 1px solid #222;
}
.community-item {
padding: 10px 12px;
background: #141414;
border-radius: 6px;
margin-bottom: 8px;
}
.community-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
}
.community-name {
font-weight: 500;
color: #e0e0e0;
}
.community-size {
font-size: 0.75rem;
color: #00d4ff;
background: #00d4ff22;
padding: 2px 8px;
border-radius: 10px;
}
.community-nodes {
display: flex;
flex-wrap: wrap;
gap: 6px;
}
.community-node-tag {
font-size: 0.75rem;
color: #888;
background: #1a1a1a;
padding: 2px 8px;
border-radius: 4px;
}
.path-viz {
flex: 1;
background: #0a0a0a;
position: relative;
overflow: hidden;
}
.path-viz svg {
width: 100%;
height: 100%;
}
.path-info {
max-height: 150px;
overflow-y: auto;
padding: 12px 16px;
background: #111;
border-top: 1px solid #222;
}
.path-step {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 0;
border-bottom: 1px solid #222;
}
.path-step:last-child {
border-bottom: none;
}
.path-step-number {
width: 24px;
height: 24px;
background: #00d4ff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: #000;
font-size: 0.75rem;
font-weight: 600;
}
.path-step-content {
flex: 1;
}
.path-step-entity {
font-weight: 500;
color: #e0e0e0;
}
.path-step-relation {
font-size: 0.8rem;
color: #00d4ff;
}
.graph-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #666;
}
.graph-empty-state-icon {
font-size: 3rem;
margin-bottom: 16px;
opacity: 0.5;
}
.graph-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #00d4ff;
}
.graph-loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #222;
border-top-color: #00d4ff;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 12px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.neo4j-status {
display: flex;
align-items: center;
gap: 6px;
font-size: 0.8rem;
padding: 4px 10px;
border-radius: 4px;
}
.neo4j-status.connected {
background: #00d4ff22;
color: #00d4ff;
}
.neo4j-status.disconnected {
background: #ff6b6b22;
color: #ff6b6b;
}
.neo4j-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: currentColor;
}
</style>
</head>
<body>
@@ -1491,6 +1945,7 @@
<button class="sidebar-btn" onclick="switchView('knowledge-base')" title="知识库">📚</button>
<button class="sidebar-btn" onclick="switchView('timeline')" title="时间线">📅</button>
<button class="sidebar-btn" onclick="switchView('reasoning')" title="智能推理">🧠</button>
<button class="sidebar-btn" onclick="switchView('graph-analysis')" title="图分析">🕸️</button>
</div>
<!-- Content Area -->
@@ -1696,6 +2151,115 @@
</div>
</div>
<!-- Graph Analysis View -->
<div id="graphAnalysisView" class="graph-analysis-panel">
<div class="graph-analysis-header">
<h2>🕸️ 图分析</h2>
<p style="color:#888;">基于 Neo4j 的图数据库分析与可视化</p>
</div>
<div class="graph-analysis-stats" id="graphStats">
<div class="graph-stat-card">
<span class="graph-stat-value" id="statNodeCount">-</span>
<span class="graph-stat-label">节点数</span>
</div>
<div class="graph-stat-card">
<span class="graph-stat-value" id="statEdgeCount">-</span>
<span class="graph-stat-label">关系数</span>
</div>
<div class="graph-stat-card">
<span class="graph-stat-value" id="statDensity">-</span>
<span class="graph-stat-label">图密度</span>
</div>
<div class="graph-stat-card">
<span class="graph-stat-value" id="statComponents">-</span>
<span class="graph-stat-label">连通分量</span>
</div>
</div>
<div class="graph-analysis-content">
<div class="graph-analysis-sidebar">
<div class="graph-analysis-section">
<h4>🔍 最短路径查询</h4>
<div class="graph-query-form">
<div class="form-group">
<label>起点实体</label>
<select id="pathStartEntity"></select>
</div>
<div class="form-group">
<label>终点实体</label>
<select id="pathEndEntity"></select>
</div>
<button class="btn btn-small" onclick="findShortestPath()">查找路径</button>
</div>
</div>
<div class="graph-analysis-section">
<h4>📊 中心性分析</h4>
<div class="centrality-controls">
<select id="centralityMetric" onchange="loadCentralityAnalysis()">
<option value="degree">度中心性</option>
<option value="betweenness">中介中心性</option>
<option value="closeness">接近中心性</option>
</select>
<button class="btn btn-small" onclick="loadCentralityAnalysis()">刷新</button>
</div>
<div class="centrality-list" id="centralityList"></div>
</div>
<div class="graph-analysis-section">
<h4>👥 社区发现</h4>
<button class="btn btn-small" onclick="loadCommunities()">分析社区</button>
<div class="communities-list" id="communitiesList"></div>
</div>
<div class="graph-analysis-section">
<h4>🌐 邻居查询</h4>
<div class="form-group">
<label>选择实体</label>
<select id="neighborEntity"></select>
</div>
<button class="btn btn-small" onclick="findNeighbors()">查找邻居</button>
</div>
</div>
<div class="graph-analysis-visualization">
<div class="graph-viz-header">
<span id="graphVizTitle">图可视化</span>
<div class="graph-viz-controls">
<button class="btn-icon" onclick="resetGraphViz()">重置</button>
<button class="btn-icon" onclick="syncToNeo4j()">同步到 Neo4j</button>
</div>
</div>
<svg id="graphAnalysisSvg"></svg>
<div class="graph-analysis-legend">
<div class="legend-item">
<span class="legend-dot" style="background:#7b2cbf"></span>
<span>项目</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background:#00d4ff"></span>
<span>技术</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background:#ff6b6b"></span>
<span>人物</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background:#4ecdc4"></span>
<span>组织</span>
</div>
<div class="legend-item">
<span class="legend-dot" style="background:#666"></span>
<span>其他</span>
</div>
</div>
</div>
</div>
<div class="graph-analysis-results" id="graphAnalysisResults"></div>
</div>
<!-- Reasoning View -->
<div id="reasoningView" class="reasoning-panel">
<div class="reasoning-header">
@@ -1759,11 +2323,9 @@
<div id="inferencePathsList"></div>
</div>
</div>
</div>
</div>
<!-- Entity Hover Card -->
<div class="entity-card" id="entityCard">
<!-- Entity Hover Card -->
<div class="entity-card" id="entityCard">
<div class="entity-card-header">
<span class="entity-type-badge" id="cardBadge">TYPE</span>
<span class="entity-card-name" id="cardName">Entity Name</span>