aifastdb-devplan 1.6.1 → 1.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev-plan-document-store.d.ts +13 -1
- package/dist/dev-plan-document-store.d.ts.map +1 -1
- package/dist/dev-plan-document-store.js +119 -0
- package/dist/dev-plan-document-store.js.map +1 -1
- package/dist/dev-plan-factory.d.ts.map +1 -1
- package/dist/dev-plan-factory.js +2 -1
- package/dist/dev-plan-factory.js.map +1 -1
- package/dist/dev-plan-graph-store.d.ts +64 -1
- package/dist/dev-plan-graph-store.d.ts.map +1 -1
- package/dist/dev-plan-graph-store.js +364 -2
- package/dist/dev-plan-graph-store.js.map +1 -1
- package/dist/dev-plan-interface.d.ts +24 -1
- package/dist/dev-plan-interface.d.ts.map +1 -1
- package/dist/dev-plan-migrate.d.ts +1 -0
- package/dist/dev-plan-migrate.d.ts.map +1 -1
- package/dist/dev-plan-migrate.js +28 -2
- package/dist/dev-plan-migrate.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server/index.js +119 -0
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/types.d.ts +88 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/visualize/graph-canvas/api-compat.d.ts.map +1 -1
- package/dist/visualize/graph-canvas/api-compat.js +22 -12
- package/dist/visualize/graph-canvas/api-compat.js.map +1 -1
- package/dist/visualize/graph-canvas/core.d.ts.map +1 -1
- package/dist/visualize/graph-canvas/core.js +296 -4
- package/dist/visualize/graph-canvas/core.js.map +1 -1
- package/dist/visualize/graph-canvas/interaction.d.ts.map +1 -1
- package/dist/visualize/graph-canvas/interaction.js +11 -0
- package/dist/visualize/graph-canvas/interaction.js.map +1 -1
- package/dist/visualize/graph-canvas/layout-worker.d.ts.map +1 -1
- package/dist/visualize/graph-canvas/layout-worker.js +45 -9
- package/dist/visualize/graph-canvas/layout-worker.js.map +1 -1
- package/dist/visualize/graph-canvas/renderer.d.ts.map +1 -1
- package/dist/visualize/graph-canvas/renderer.js +164 -33
- package/dist/visualize/graph-canvas/renderer.js.map +1 -1
- package/dist/visualize/graph-canvas/styles.d.ts.map +1 -1
- package/dist/visualize/graph-canvas/styles.js +136 -121
- package/dist/visualize/graph-canvas/styles.js.map +1 -1
- package/dist/visualize/graph-canvas/viewport.d.ts.map +1 -1
- package/dist/visualize/graph-canvas/viewport.js +10 -0
- package/dist/visualize/graph-canvas/viewport.js.map +1 -1
- package/dist/visualize/server.js +149 -32
- package/dist/visualize/server.js.map +1 -1
- package/dist/visualize/template-core.d.ts +9 -0
- package/dist/visualize/template-core.d.ts.map +1 -0
- package/dist/visualize/template-core.js +714 -0
- package/dist/visualize/template-core.js.map +1 -0
- package/dist/visualize/template-data-loading.d.ts +7 -0
- package/dist/visualize/template-data-loading.d.ts.map +1 -0
- package/dist/visualize/template-data-loading.js +677 -0
- package/dist/visualize/template-data-loading.js.map +1 -0
- package/dist/visualize/template-detail-panel.d.ts +14 -0
- package/dist/visualize/template-detail-panel.d.ts.map +1 -0
- package/dist/visualize/template-detail-panel.js +553 -0
- package/dist/visualize/template-detail-panel.js.map +1 -0
- package/dist/visualize/template-graph-3d.d.ts +7 -0
- package/dist/visualize/template-graph-3d.d.ts.map +1 -0
- package/dist/visualize/template-graph-3d.js +1112 -0
- package/dist/visualize/template-graph-3d.js.map +1 -0
- package/dist/visualize/template-graph-vis.d.ts +8 -0
- package/dist/visualize/template-graph-vis.d.ts.map +1 -0
- package/dist/visualize/template-graph-vis.js +1204 -0
- package/dist/visualize/template-graph-vis.js.map +1 -0
- package/dist/visualize/template-html.d.ts +9 -0
- package/dist/visualize/template-html.d.ts.map +1 -0
- package/dist/visualize/template-html.js +484 -0
- package/dist/visualize/template-html.js.map +1 -0
- package/dist/visualize/template-pages.d.ts +7 -0
- package/dist/visualize/template-pages.d.ts.map +1 -0
- package/dist/visualize/template-pages.js +806 -0
- package/dist/visualize/template-pages.js.map +1 -0
- package/dist/visualize/template-stats-modal.d.ts +7 -0
- package/dist/visualize/template-stats-modal.d.ts.map +1 -0
- package/dist/visualize/template-stats-modal.js +406 -0
- package/dist/visualize/template-stats-modal.js.map +1 -0
- package/dist/visualize/template-styles.d.ts +9 -0
- package/dist/visualize/template-styles.d.ts.map +1 -0
- package/dist/visualize/template-styles.js +487 -0
- package/dist/visualize/template-styles.js.map +1 -0
- package/dist/visualize/template.d.ts +14 -3
- package/dist/visualize/template.d.ts.map +1 -1
- package/dist/visualize/template.js +38 -3475
- package/dist/visualize/template.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-core.js","sourceRoot":"","sources":["../../src/visualize/template-core.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAEH,sCA8rBC;AA9rBD,SAAgB,aAAa;IAC3B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4rBR,CAAC;AACF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"template-data-loading.d.ts","sourceRoot":"","sources":["../../src/visualize/template-data-loading.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,wBAAgB,oBAAoB,IAAI,MAAM,CA2pB7C"}
|
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DevPlan 图可视化 — 数据加载模块
|
|
4
|
+
*
|
|
5
|
+
* 包含: API 数据获取、分层加载、概览模式、节点/边数据处理。
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.getDataLoadingScript = getDataLoadingScript;
|
|
9
|
+
function getDataLoadingScript() {
|
|
10
|
+
return `
|
|
11
|
+
// ========== Data Loading ==========
|
|
12
|
+
// ── Phase-8C: Chunked loading configuration ──
|
|
13
|
+
var CHUNK_SIZE = 5000; // nodes per page
|
|
14
|
+
var CHUNK_THRESHOLD = 3000; // use chunked loading if total > this
|
|
15
|
+
|
|
16
|
+
function loadData() {
|
|
17
|
+
document.getElementById('loading').style.display = 'flex';
|
|
18
|
+
log('正在获取图谱数据...', true);
|
|
19
|
+
|
|
20
|
+
// 统一使用全量加载(vis-network 和 3D Force Graph 均适用)
|
|
21
|
+
loadDataFull();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Phase-10 T10.1: Tiered loading for vis-network.
|
|
26
|
+
* First loads L0+L1 (project, module, main-task) → fast first screen.
|
|
27
|
+
* Sub-tasks and documents loaded on demand via double-click or filter toggle.
|
|
28
|
+
*/
|
|
29
|
+
function loadDataTiered() {
|
|
30
|
+
log('分层加载: 首屏仅加载核心节点 (project/module/main-task)...', true);
|
|
31
|
+
tieredLoadState = { l0l1Loaded: false, l2Loaded: false, l3Loaded: false, expandedPhases: {}, totalNodes: 0 };
|
|
32
|
+
networkReusable = false;
|
|
33
|
+
|
|
34
|
+
// Fetch L0+L1 nodes + progress in parallel
|
|
35
|
+
var pagedUrl = '/api/graph/paged?offset=0&limit=500' +
|
|
36
|
+
'&entityTypes=' + TIER_L0L1_TYPES.join(',') +
|
|
37
|
+
'&includeDocuments=false&includeModules=true';
|
|
38
|
+
|
|
39
|
+
Promise.all([
|
|
40
|
+
fetch(pagedUrl).then(function(r) { return r.json(); }),
|
|
41
|
+
fetch('/api/progress').then(function(r) { return r.json(); })
|
|
42
|
+
]).then(function(results) {
|
|
43
|
+
var graphRes = results[0];
|
|
44
|
+
var progressRes = results[1];
|
|
45
|
+
allNodes = graphRes.nodes || [];
|
|
46
|
+
allEdges = graphRes.edges || [];
|
|
47
|
+
tieredLoadState.l0l1Loaded = true;
|
|
48
|
+
tieredLoadState.totalNodes = graphRes.total || allNodes.length;
|
|
49
|
+
|
|
50
|
+
log('首屏数据: ' + allNodes.length + ' 核心节点, ' + allEdges.length + ' 边 (总计 ' + tieredLoadState.totalNodes + ')', true);
|
|
51
|
+
renderStats(progressRes, graphRes);
|
|
52
|
+
renderGraph();
|
|
53
|
+
updateTieredIndicator();
|
|
54
|
+
// 分层模式: 子任务和文档尚未加载,在图例上给出视觉提示
|
|
55
|
+
markUnloadedTypeLegends();
|
|
56
|
+
}).catch(function(err) {
|
|
57
|
+
log('分层加载失败: ' + err.message + ', 回退全量加载', false);
|
|
58
|
+
// Fallback: full load
|
|
59
|
+
loadDataFull();
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Phase-10: Full load fallback (same as original loadData for vis-network) */
|
|
64
|
+
function loadDataFull() {
|
|
65
|
+
var graphApiUrl = '/api/graph?includeNodeDegree=' + (INCLUDE_NODE_DEGREE ? 'true' : 'false') +
|
|
66
|
+
'&enableBackendDegreeFallback=' + (ENABLE_BACKEND_DEGREE_FALLBACK ? 'true' : 'false');
|
|
67
|
+
Promise.all([
|
|
68
|
+
fetch(graphApiUrl).then(function(r) { return r.json(); }),
|
|
69
|
+
fetch('/api/progress').then(function(r) { return r.json(); })
|
|
70
|
+
]).then(function(results) {
|
|
71
|
+
var graphRes = results[0];
|
|
72
|
+
var progressRes = results[1];
|
|
73
|
+
allNodes = graphRes.nodes || [];
|
|
74
|
+
allEdges = graphRes.edges || [];
|
|
75
|
+
tieredLoadState.l0l1Loaded = true;
|
|
76
|
+
tieredLoadState.l2Loaded = true;
|
|
77
|
+
tieredLoadState.l3Loaded = true;
|
|
78
|
+
tieredLoadState.totalNodes = allNodes.length;
|
|
79
|
+
networkReusable = false; // Force full rebuild
|
|
80
|
+
// 全量加载完成:清除所有隐藏状态 + 未加载标记,同步图例为全部激活
|
|
81
|
+
hiddenTypes = {};
|
|
82
|
+
clearUnloadedTypeLegends();
|
|
83
|
+
syncLegendToggleState();
|
|
84
|
+
log('全量数据: ' + allNodes.length + ' 节点, ' + allEdges.length + ' 边', true);
|
|
85
|
+
renderStats(progressRes, graphRes);
|
|
86
|
+
renderGraph();
|
|
87
|
+
updateTieredIndicator();
|
|
88
|
+
}).catch(function(err) {
|
|
89
|
+
log('数据获取失败: ' + err.message, false);
|
|
90
|
+
document.getElementById('loading').innerHTML = '<div style="text-align:center"><div style="font-size:48px;margin-bottom:16px;">⚠️</div><p style="color:#f87171;">数据加载失败: ' + err.message + '</p><button class="refresh-btn" onclick="loadData()" style="margin-top:12px;">重试</button></div>';
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Phase-10 T10.1+T10.5: Load sub-tasks for a specific main-task (on demand).
|
|
96
|
+
* Called when user double-clicks a main-task node to expand it.
|
|
97
|
+
*/
|
|
98
|
+
function loadSubTasksForPhase(phaseTaskId) {
|
|
99
|
+
if (tieredLoadState.expandedPhases[phaseTaskId]) {
|
|
100
|
+
// Already expanded → collapse (remove sub-task nodes)
|
|
101
|
+
collapsePhaseSubTasks(phaseTaskId);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
log('加载子任务: ' + phaseTaskId + '...', true);
|
|
105
|
+
|
|
106
|
+
// Fetch sub-tasks: use full paged API with entity type filter
|
|
107
|
+
var pagedUrl = '/api/graph/paged?offset=0&limit=2000' +
|
|
108
|
+
'&entityTypes=' + TIER_L2_TYPES.concat(TIER_L3_TYPES).join(',');
|
|
109
|
+
|
|
110
|
+
fetch(pagedUrl).then(function(r) { return r.json(); }).then(function(result) {
|
|
111
|
+
var newNodes = result.nodes || [];
|
|
112
|
+
var newEdges = result.edges || [];
|
|
113
|
+
|
|
114
|
+
// Filter to only sub-tasks/docs connected to this phase
|
|
115
|
+
var phaseNodeIds = {};
|
|
116
|
+
phaseNodeIds[phaseTaskId] = true;
|
|
117
|
+
// Find edges from this phase to sub-tasks
|
|
118
|
+
var childIds = {};
|
|
119
|
+
for (var i = 0; i < newEdges.length; i++) {
|
|
120
|
+
if (newEdges[i].from === phaseTaskId) {
|
|
121
|
+
childIds[newEdges[i].to] = true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
// Also get docs linked to this phase
|
|
125
|
+
for (var i = 0; i < newEdges.length; i++) {
|
|
126
|
+
if (childIds[newEdges[i].from]) {
|
|
127
|
+
childIds[newEdges[i].to] = true;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
var addedNodes = [];
|
|
132
|
+
var addedEdges = [];
|
|
133
|
+
var existingIds = {};
|
|
134
|
+
for (var i = 0; i < allNodes.length; i++) existingIds[allNodes[i].id] = true;
|
|
135
|
+
|
|
136
|
+
for (var i = 0; i < newNodes.length; i++) {
|
|
137
|
+
var n = newNodes[i];
|
|
138
|
+
if (childIds[n.id] && !existingIds[n.id]) {
|
|
139
|
+
allNodes.push(n);
|
|
140
|
+
addedNodes.push(n);
|
|
141
|
+
existingIds[n.id] = true;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
for (var i = 0; i < newEdges.length; i++) {
|
|
145
|
+
var e = newEdges[i];
|
|
146
|
+
if (existingIds[e.from] && existingIds[e.to]) {
|
|
147
|
+
// Check if edge already exists
|
|
148
|
+
var edgeExists = false;
|
|
149
|
+
for (var j = 0; j < allEdges.length; j++) {
|
|
150
|
+
if (allEdges[j].from === e.from && allEdges[j].to === e.to) { edgeExists = true; break; }
|
|
151
|
+
}
|
|
152
|
+
if (!edgeExists) {
|
|
153
|
+
allEdges.push(e);
|
|
154
|
+
addedEdges.push(e);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
tieredLoadState.expandedPhases[phaseTaskId] = true;
|
|
160
|
+
log('已展开 ' + phaseTaskId + ': +' + addedNodes.length + ' 节点, +' + addedEdges.length + ' 边', true);
|
|
161
|
+
|
|
162
|
+
// Phase-10 T10.3: Incremental update instead of full rebuild
|
|
163
|
+
if (networkReusable && nodesDataSet && edgesDataSet && network) {
|
|
164
|
+
incrementalAddNodes(addedNodes, addedEdges);
|
|
165
|
+
} else {
|
|
166
|
+
renderGraph();
|
|
167
|
+
}
|
|
168
|
+
updateTieredIndicator();
|
|
169
|
+
}).catch(function(err) {
|
|
170
|
+
log('加载子任务失败: ' + err.message, false);
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Phase-10 T10.5: Collapse sub-tasks of a phase (remove from graph).
|
|
176
|
+
*/
|
|
177
|
+
function collapsePhaseSubTasks(phaseTaskId) {
|
|
178
|
+
var removeIds = {};
|
|
179
|
+
// Find sub-task/document nodes that were added for this phase
|
|
180
|
+
for (var i = 0; i < allEdges.length; i++) {
|
|
181
|
+
if (allEdges[i].from === phaseTaskId) {
|
|
182
|
+
var targetType = getNodeTypeById(allEdges[i].to);
|
|
183
|
+
if (targetType === 'sub-task' || targetType === 'document') {
|
|
184
|
+
removeIds[allEdges[i].to] = true;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Also remove documents connected to removed sub-tasks
|
|
189
|
+
for (var i = 0; i < allEdges.length; i++) {
|
|
190
|
+
if (removeIds[allEdges[i].from]) {
|
|
191
|
+
var targetType = getNodeTypeById(allEdges[i].to);
|
|
192
|
+
if (targetType === 'document') {
|
|
193
|
+
removeIds[allEdges[i].to] = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Remove from allNodes/allEdges
|
|
199
|
+
allNodes = allNodes.filter(function(n) { return !removeIds[n.id]; });
|
|
200
|
+
allEdges = allEdges.filter(function(e) { return !removeIds[e.from] && !removeIds[e.to]; });
|
|
201
|
+
|
|
202
|
+
delete tieredLoadState.expandedPhases[phaseTaskId];
|
|
203
|
+
log('已收起 ' + phaseTaskId + ': 移除 ' + Object.keys(removeIds).length + ' 节点', true);
|
|
204
|
+
|
|
205
|
+
// Phase-10 T10.3: Incremental remove
|
|
206
|
+
if (networkReusable && nodesDataSet && edgesDataSet && network) {
|
|
207
|
+
incrementalRemoveNodes(Object.keys(removeIds));
|
|
208
|
+
} else {
|
|
209
|
+
renderGraph();
|
|
210
|
+
}
|
|
211
|
+
updateTieredIndicator();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Phase-10: Helper to get node type by ID from allNodes */
|
|
215
|
+
function getNodeTypeById(nodeId) {
|
|
216
|
+
for (var i = 0; i < allNodes.length; i++) {
|
|
217
|
+
if (allNodes[i].id === nodeId) return allNodes[i].type;
|
|
218
|
+
}
|
|
219
|
+
return '';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Phase-10 T10.3: Incrementally add styled nodes/edges to DataSet (no rebuild).
|
|
224
|
+
*/
|
|
225
|
+
function incrementalAddNodes(rawNodes, rawEdges) {
|
|
226
|
+
if (!nodesDataSet || !edgesDataSet) return;
|
|
227
|
+
var addedVisNodes = [];
|
|
228
|
+
for (var i = 0; i < rawNodes.length; i++) {
|
|
229
|
+
var n = rawNodes[i];
|
|
230
|
+
if (hiddenTypes[n.type]) continue;
|
|
231
|
+
var deg = getNodeDegree(n);
|
|
232
|
+
var s = nodeStyle(n, deg);
|
|
233
|
+
addedVisNodes.push({
|
|
234
|
+
id: n.id, label: n.label, _origLabel: n.label,
|
|
235
|
+
title: n.label + ' (连接: ' + deg + ')',
|
|
236
|
+
shape: s.shape, size: s.size, color: s.color, font: s.font,
|
|
237
|
+
borderWidth: s.borderWidth, _type: n.type,
|
|
238
|
+
_props: n.properties || {},
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
var addedVisEdges = [];
|
|
242
|
+
var existingNodeIds = {};
|
|
243
|
+
var currentIds = nodesDataSet.getIds();
|
|
244
|
+
for (var i = 0; i < currentIds.length; i++) existingNodeIds[currentIds[i]] = true;
|
|
245
|
+
for (var i = 0; i < addedVisNodes.length; i++) existingNodeIds[addedVisNodes[i].id] = true;
|
|
246
|
+
|
|
247
|
+
for (var i = 0; i < rawEdges.length; i++) {
|
|
248
|
+
var e = rawEdges[i];
|
|
249
|
+
if (!existingNodeIds[e.from] || !existingNodeIds[e.to]) continue;
|
|
250
|
+
var es = edgeStyle(e);
|
|
251
|
+
addedVisEdges.push({
|
|
252
|
+
id: 'e_inc_' + Date.now() + '_' + i, from: e.from, to: e.to,
|
|
253
|
+
width: es.width, _origWidth: es.width,
|
|
254
|
+
color: es.color, dashes: es.dashes, arrows: es.arrows,
|
|
255
|
+
_label: e.label, _highlightColor: es._highlightColor || '#9ca3af',
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (addedVisNodes.length > 0) nodesDataSet.add(addedVisNodes);
|
|
260
|
+
if (addedVisEdges.length > 0) edgesDataSet.add(addedVisEdges);
|
|
261
|
+
|
|
262
|
+
// Brief physics to settle new nodes, then stop
|
|
263
|
+
if (network && addedVisNodes.length > 0) {
|
|
264
|
+
network.setOptions({ physics: { enabled: true, stabilization: { enabled: false } } });
|
|
265
|
+
setTimeout(function() {
|
|
266
|
+
if (network) network.setOptions({ physics: { enabled: false } });
|
|
267
|
+
}, 1500);
|
|
268
|
+
}
|
|
269
|
+
log('增量添加: +' + addedVisNodes.length + ' 节点, +' + addedVisEdges.length + ' 边', true);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Phase-10 T10.3: Incrementally remove nodes/edges from DataSet (no rebuild).
|
|
274
|
+
*/
|
|
275
|
+
function incrementalRemoveNodes(nodeIds) {
|
|
276
|
+
if (!nodesDataSet || !edgesDataSet) return;
|
|
277
|
+
// Remove edges first
|
|
278
|
+
var removeEdgeIds = [];
|
|
279
|
+
var removeSet = {};
|
|
280
|
+
for (var i = 0; i < nodeIds.length; i++) removeSet[nodeIds[i]] = true;
|
|
281
|
+
edgesDataSet.forEach(function(edge) {
|
|
282
|
+
if (removeSet[edge.from] || removeSet[edge.to]) {
|
|
283
|
+
removeEdgeIds.push(edge.id);
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
if (removeEdgeIds.length > 0) edgesDataSet.remove(removeEdgeIds);
|
|
287
|
+
if (nodeIds.length > 0) nodesDataSet.remove(nodeIds);
|
|
288
|
+
log('增量移除: -' + nodeIds.length + ' 节点, -' + removeEdgeIds.length + ' 边', true);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Phase-10 T10.2+T10.1: Load all nodes (switch from tiered to full mode).
|
|
293
|
+
*/
|
|
294
|
+
function loadAllNodes() {
|
|
295
|
+
var btn = document.getElementById('loadAllBtn');
|
|
296
|
+
if (btn) btn.textContent = '加载中...';
|
|
297
|
+
log('加载全部节点...', true);
|
|
298
|
+
|
|
299
|
+
loadDataFull();
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** Phase-10: Update tiered loading indicator in the UI */
|
|
303
|
+
function updateTieredIndicator() {
|
|
304
|
+
var indicator = document.getElementById('tieredIndicator');
|
|
305
|
+
var loadAllBtn = document.getElementById('loadAllBtn');
|
|
306
|
+
if (!indicator || !loadAllBtn) return;
|
|
307
|
+
|
|
308
|
+
if (!USE_3D && tieredLoadState.l0l1Loaded && !tieredLoadState.l2Loaded) {
|
|
309
|
+
// Tiered mode active
|
|
310
|
+
var expandedCount = Object.keys(tieredLoadState.expandedPhases).length;
|
|
311
|
+
indicator.style.display = 'inline';
|
|
312
|
+
indicator.textContent = '分层 ' + allNodes.length + '/' + tieredLoadState.totalNodes;
|
|
313
|
+
if (expandedCount > 0) {
|
|
314
|
+
indicator.textContent += ' (展开' + expandedCount + ')';
|
|
315
|
+
}
|
|
316
|
+
loadAllBtn.style.display = 'inline-flex';
|
|
317
|
+
loadAllBtn.textContent = '全部';
|
|
318
|
+
} else {
|
|
319
|
+
indicator.style.display = 'none';
|
|
320
|
+
loadAllBtn.style.display = 'none';
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* Phase-10 T10.2: Overview mode — show one super-node per entity type.
|
|
326
|
+
* Uses /api/graph/clusters for aggregated data.
|
|
327
|
+
*/
|
|
328
|
+
var overviewModeActive = false;
|
|
329
|
+
var overviewSavedState = null; // { allNodes, allEdges, nodesDataSet, edgesDataSet }
|
|
330
|
+
|
|
331
|
+
function toggleOverviewMode() {
|
|
332
|
+
var btn = document.getElementById('overviewBtn');
|
|
333
|
+
if (overviewModeActive) {
|
|
334
|
+
// Exit overview → restore saved state
|
|
335
|
+
exitOverviewMode();
|
|
336
|
+
if (btn) btn.textContent = '概览';
|
|
337
|
+
if (btn) btn.style.color = '';
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
// Enter overview
|
|
341
|
+
if (btn) btn.textContent = '退出概览';
|
|
342
|
+
if (btn) btn.style.color = '#f59e0b';
|
|
343
|
+
enterOverviewMode();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function enterOverviewMode() {
|
|
347
|
+
log('概览模式: 获取聚合数据...', true);
|
|
348
|
+
// Save current state
|
|
349
|
+
overviewSavedState = { allNodes: allNodes, allEdges: allEdges };
|
|
350
|
+
|
|
351
|
+
fetch('/api/graph/clusters').then(function(r) { return r.json(); }).then(function(data) {
|
|
352
|
+
if (!data || !data.groups) {
|
|
353
|
+
log('聚合数据为空, 保持当前视图', false);
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
overviewModeActive = true;
|
|
358
|
+
|
|
359
|
+
// Build super-nodes: one per entity type group
|
|
360
|
+
var groups = data.groups;
|
|
361
|
+
var typeNames = { 'devplan-project': '项目', 'devplan-module': '模块', 'devplan-main-task': '主任务', 'devplan-sub-task': '子任务', 'devplan-document': '文档' };
|
|
362
|
+
var typeColors = { 'devplan-project': '#f59e0b', 'devplan-module': '#ff6600', 'devplan-main-task': '#047857', 'devplan-sub-task': '#e8956a', 'devplan-document': '#2563eb' };
|
|
363
|
+
var typeShapes = { 'devplan-project': 'star', 'devplan-module': 'diamond', 'devplan-main-task': 'dot', 'devplan-sub-task': 'dot', 'devplan-document': 'box' };
|
|
364
|
+
|
|
365
|
+
var overviewNodes = [];
|
|
366
|
+
var typeIds = Object.keys(groups);
|
|
367
|
+
for (var i = 0; i < typeIds.length; i++) {
|
|
368
|
+
var typeId = typeIds[i];
|
|
369
|
+
var g = groups[typeId];
|
|
370
|
+
var count = g.count || 0;
|
|
371
|
+
if (count === 0) continue;
|
|
372
|
+
var displayName = typeNames[typeId] || typeId;
|
|
373
|
+
var color = typeColors[typeId] || '#6b7280';
|
|
374
|
+
overviewNodes.push({
|
|
375
|
+
id: 'overview_' + typeId,
|
|
376
|
+
label: displayName + '\\n(' + count + ')',
|
|
377
|
+
_origLabel: displayName,
|
|
378
|
+
title: displayName + ': ' + count + ' 个节点\\n点击展开此类型',
|
|
379
|
+
shape: typeShapes[typeId] || 'dot',
|
|
380
|
+
size: Math.min(20 + Math.sqrt(count) * 3, 60),
|
|
381
|
+
color: { background: color, border: color, highlight: { background: color, border: '#fff' } },
|
|
382
|
+
font: { size: 14, color: '#e5e7eb' },
|
|
383
|
+
borderWidth: 3,
|
|
384
|
+
_type: typeId,
|
|
385
|
+
_props: { status: 'active', count: count, sampleIds: g.sample_ids || [] },
|
|
386
|
+
});
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Edges: connect project to modules, modules to main-tasks, etc.
|
|
390
|
+
var overviewEdges = [];
|
|
391
|
+
var edgeIdx = 0;
|
|
392
|
+
if (groups['devplan-project'] && groups['devplan-module']) {
|
|
393
|
+
overviewEdges.push({ id: 'oe' + (edgeIdx++), from: 'overview_devplan-project', to: 'overview_devplan-module', width: 2, color: { color: '#4b5563' }, arrows: { to: { enabled: true, scaleFactor: 0.5 } } });
|
|
394
|
+
}
|
|
395
|
+
if (groups['devplan-module'] && groups['devplan-main-task']) {
|
|
396
|
+
overviewEdges.push({ id: 'oe' + (edgeIdx++), from: 'overview_devplan-module', to: 'overview_devplan-main-task', width: 2, color: { color: '#4b5563' }, arrows: { to: { enabled: true, scaleFactor: 0.5 } } });
|
|
397
|
+
}
|
|
398
|
+
if (groups['devplan-main-task'] && groups['devplan-sub-task']) {
|
|
399
|
+
overviewEdges.push({ id: 'oe' + (edgeIdx++), from: 'overview_devplan-main-task', to: 'overview_devplan-sub-task', width: 1.5, color: { color: '#4b5563' }, arrows: { to: { enabled: true, scaleFactor: 0.4 } } });
|
|
400
|
+
}
|
|
401
|
+
if (groups['devplan-main-task'] && groups['devplan-document']) {
|
|
402
|
+
overviewEdges.push({ id: 'oe' + (edgeIdx++), from: 'overview_devplan-main-task', to: 'overview_devplan-document', width: 1, color: { color: '#4b5563' }, dashes: [5, 5], arrows: { to: { enabled: true, scaleFactor: 0.4 } } });
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
log('概览模式: ' + overviewNodes.length + ' 类型节点', true);
|
|
406
|
+
|
|
407
|
+
// Replace the current graph
|
|
408
|
+
allNodes = [];
|
|
409
|
+
allEdges = [];
|
|
410
|
+
if (nodesDataSet) {
|
|
411
|
+
var allIds = nodesDataSet.getIds();
|
|
412
|
+
if (allIds.length > 0) nodesDataSet.remove(allIds);
|
|
413
|
+
nodesDataSet.add(overviewNodes);
|
|
414
|
+
}
|
|
415
|
+
if (edgesDataSet) {
|
|
416
|
+
var allEIds = edgesDataSet.getIds();
|
|
417
|
+
if (allEIds.length > 0) edgesDataSet.remove(allEIds);
|
|
418
|
+
edgesDataSet.add(overviewEdges);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Brief physics to arrange
|
|
422
|
+
if (network) {
|
|
423
|
+
network.setOptions({
|
|
424
|
+
physics: { enabled: true, solver: 'forceAtlas2Based',
|
|
425
|
+
forceAtlas2Based: { gravitationalConstant: -50, centralGravity: 0.05, springLength: 200, springConstant: 0.08, damping: 0.5 },
|
|
426
|
+
stabilization: { enabled: true, iterations: 50, updateInterval: 10 }
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
}).catch(function(err) {
|
|
431
|
+
log('概览模式失败: ' + err.message, false);
|
|
432
|
+
overviewModeActive = false;
|
|
433
|
+
var btn = document.getElementById('overviewBtn');
|
|
434
|
+
if (btn) { btn.textContent = '概览'; btn.style.color = ''; }
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
function exitOverviewMode() {
|
|
439
|
+
overviewModeActive = false;
|
|
440
|
+
if (overviewSavedState) {
|
|
441
|
+
allNodes = overviewSavedState.allNodes;
|
|
442
|
+
allEdges = overviewSavedState.allEdges;
|
|
443
|
+
overviewSavedState = null;
|
|
444
|
+
}
|
|
445
|
+
// Force full rebuild to restore normal view
|
|
446
|
+
networkReusable = false;
|
|
447
|
+
renderGraph();
|
|
448
|
+
updateTieredIndicator();
|
|
449
|
+
log('已退出概览模式', true);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Phase-8C T8C.3+T8C.4: Chunked progressive rendering for large datasets.
|
|
454
|
+
* Renders the first CHUNK_SIZE nodes immediately, then loads remaining chunks
|
|
455
|
+
* in the background using addNodes/addEdges incremental API.
|
|
456
|
+
*/
|
|
457
|
+
function renderGraphChunked() {
|
|
458
|
+
try {
|
|
459
|
+
var container = document.getElementById('graph');
|
|
460
|
+
var rect = container.getBoundingClientRect();
|
|
461
|
+
if (rect.height < 50) {
|
|
462
|
+
container.style.height = (window.innerHeight - 140) + 'px';
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Sort nodes: center-priority (closest to centroid first)
|
|
466
|
+
var cx = 0, cy = 0;
|
|
467
|
+
for (var i = 0; i < allNodes.length; i++) {
|
|
468
|
+
cx += (allNodes[i].x || 0);
|
|
469
|
+
cy += (allNodes[i].y || 0);
|
|
470
|
+
}
|
|
471
|
+
if (allNodes.length > 0) { cx /= allNodes.length; cy /= allNodes.length; }
|
|
472
|
+
var sortedNodes = allNodes.slice().sort(function(a, b) {
|
|
473
|
+
var da = Math.pow(((a.x||0) - cx), 2) + Math.pow(((a.y||0) - cy), 2);
|
|
474
|
+
var db = Math.pow(((b.x||0) - cx), 2) + Math.pow(((b.y||0) - cy), 2);
|
|
475
|
+
return da - db;
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// First chunk
|
|
479
|
+
var firstChunkNodes = sortedNodes.slice(0, CHUNK_SIZE);
|
|
480
|
+
var firstChunkIds = {};
|
|
481
|
+
for (var i = 0; i < firstChunkNodes.length; i++) firstChunkIds[firstChunkNodes[i].id] = true;
|
|
482
|
+
var firstChunkEdges = [];
|
|
483
|
+
for (var i = 0; i < allEdges.length; i++) {
|
|
484
|
+
if (firstChunkIds[allEdges[i].from] && firstChunkIds[allEdges[i].to]) {
|
|
485
|
+
firstChunkEdges.push(allEdges[i]);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// Prepare first chunk visible nodes/edges (same transform as renderGraph)
|
|
490
|
+
var visibleNodes = [];
|
|
491
|
+
for (var i = 0; i < firstChunkNodes.length; i++) {
|
|
492
|
+
var n = firstChunkNodes[i];
|
|
493
|
+
if (hiddenTypes[n.type]) continue;
|
|
494
|
+
if (isNodeCollapsedByParent(n.id)) continue;
|
|
495
|
+
var deg = getNodeDegree(n);
|
|
496
|
+
var s = nodeStyle(n, deg);
|
|
497
|
+
visibleNodes.push({
|
|
498
|
+
id: n.id, label: n.label, _origLabel: n.label,
|
|
499
|
+
title: n.label + ' (连接: ' + deg + ')',
|
|
500
|
+
shape: s.shape, size: s.size, color: s.color, font: s.font,
|
|
501
|
+
borderWidth: s.borderWidth, _type: n.type,
|
|
502
|
+
_props: n.properties || {}, _isParentDoc: isParentDocNode(n),
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
var visibleIds = {};
|
|
506
|
+
var _chunkProjectIds = {};
|
|
507
|
+
for (var i = 0; i < visibleNodes.length; i++) {
|
|
508
|
+
visibleIds[visibleNodes[i].id] = true;
|
|
509
|
+
if (visibleNodes[i]._type === 'project') _chunkProjectIds[visibleNodes[i].id] = true;
|
|
510
|
+
}
|
|
511
|
+
var _chunkGraphSettings = getGraphSettings();
|
|
512
|
+
var _chunkHideProjectEdges = !_chunkGraphSettings.showProjectEdges;
|
|
513
|
+
var visibleEdges = [];
|
|
514
|
+
for (var i = 0; i < firstChunkEdges.length; i++) {
|
|
515
|
+
var e = firstChunkEdges[i];
|
|
516
|
+
if (!visibleIds[e.from] || !visibleIds[e.to]) continue;
|
|
517
|
+
var _chunkIsProjectEdge = _chunkHideProjectEdges && (_chunkProjectIds[e.from] || _chunkProjectIds[e.to]);
|
|
518
|
+
var es = edgeStyle(e);
|
|
519
|
+
visibleEdges.push({
|
|
520
|
+
id: 'e' + i, from: e.from, to: e.to,
|
|
521
|
+
width: es.width, _origWidth: es.width,
|
|
522
|
+
color: es.color, dashes: es.dashes, arrows: es.arrows,
|
|
523
|
+
_label: e.label, _highlightColor: es._highlightColor || '#9ca3af',
|
|
524
|
+
_projectEdgeHidden: !!_chunkIsProjectEdge, hidden: !!_chunkIsProjectEdge,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
log('分块加载: 首批 ' + visibleNodes.length + '/' + allNodes.length + ' 节点', true);
|
|
529
|
+
|
|
530
|
+
if (network) { network.destroy(); network = null; }
|
|
531
|
+
|
|
532
|
+
// Create network with first chunk
|
|
533
|
+
nodesDataSet = new SimpleDataSet(visibleNodes);
|
|
534
|
+
edgesDataSet = new SimpleDataSet(visibleEdges);
|
|
535
|
+
|
|
536
|
+
var networkOptions = {
|
|
537
|
+
nodes: { borderWidth: 2, shadow: { enabled: true, color: 'rgba(0,0,0,0.3)', size: 5, x: 0, y: 2 } },
|
|
538
|
+
edges: { smooth: { enabled: true, type: 'continuous', roundness: 0.5 }, shadow: false },
|
|
539
|
+
physics: { enabled: true, solver: 'forceAtlas2Based',
|
|
540
|
+
forceAtlas2Based: { gravitationalConstant: -80, centralGravity: 0.015, springLength: 150, springConstant: 0.05, damping: 0.4, avoidOverlap: 0.8 },
|
|
541
|
+
stabilization: { enabled: true, iterations: 200, updateInterval: 25 }
|
|
542
|
+
},
|
|
543
|
+
interaction: { hover: true, tooltipDelay: 100, navigationButtons: false, keyboard: false, zoomView: true, dragView: true },
|
|
544
|
+
layout: { improvedLayout: false, hierarchical: false }
|
|
545
|
+
};
|
|
546
|
+
|
|
547
|
+
network = new DevPlanGraph(container, { nodes: visibleNodes, edges: visibleEdges }, networkOptions);
|
|
548
|
+
|
|
549
|
+
// Show loading indicator with progress
|
|
550
|
+
document.getElementById('loading').style.display = 'none';
|
|
551
|
+
log('首批数据已渲染,后台加载剩余 ' + (sortedNodes.length - CHUNK_SIZE) + ' 节点...', true);
|
|
552
|
+
|
|
553
|
+
// ── Progressive background loading ──
|
|
554
|
+
var loadedNodeIds = Object.assign({}, firstChunkIds);
|
|
555
|
+
var chunkIndex = 1;
|
|
556
|
+
var totalChunks = Math.ceil(sortedNodes.length / CHUNK_SIZE);
|
|
557
|
+
|
|
558
|
+
function loadNextChunk() {
|
|
559
|
+
var start = chunkIndex * CHUNK_SIZE;
|
|
560
|
+
var end = Math.min(start + CHUNK_SIZE, sortedNodes.length);
|
|
561
|
+
if (start >= sortedNodes.length) {
|
|
562
|
+
log('✅ 全部数据加载完成: ' + allNodes.length + ' 节点, ' + allEdges.length + ' 边', true);
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
var chunkNodes = [];
|
|
567
|
+
for (var i = start; i < end; i++) {
|
|
568
|
+
var n = sortedNodes[i];
|
|
569
|
+
if (hiddenTypes[n.type]) continue;
|
|
570
|
+
if (isNodeCollapsedByParent(n.id)) continue;
|
|
571
|
+
var deg = getNodeDegree(n);
|
|
572
|
+
var s = nodeStyle(n, deg);
|
|
573
|
+
chunkNodes.push({
|
|
574
|
+
id: n.id, label: n.label, _origLabel: n.label,
|
|
575
|
+
title: n.label, shape: s.shape, size: s.size,
|
|
576
|
+
color: s.color, font: s.font, borderWidth: s.borderWidth,
|
|
577
|
+
_type: n.type, _props: n.properties || {},
|
|
578
|
+
x: n.x || 0, y: n.y || 0,
|
|
579
|
+
});
|
|
580
|
+
loadedNodeIds[n.id] = true;
|
|
581
|
+
if (n.type === 'project') _chunkProjectIds[n.id] = true;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// Edges for this chunk (both endpoints must be loaded)
|
|
585
|
+
var chunkEdges = [];
|
|
586
|
+
for (var i = 0; i < allEdges.length; i++) {
|
|
587
|
+
var e = allEdges[i];
|
|
588
|
+
if (loadedNodeIds[e.from] && loadedNodeIds[e.to]) {
|
|
589
|
+
var _chkIsProjectEdge = _chunkHideProjectEdges && (_chunkProjectIds[e.from] || _chunkProjectIds[e.to]);
|
|
590
|
+
var es = edgeStyle(e);
|
|
591
|
+
chunkEdges.push({
|
|
592
|
+
id: 'ec' + chunkIndex + '_' + i, from: e.from, to: e.to,
|
|
593
|
+
width: es.width, _origWidth: es.width,
|
|
594
|
+
color: es.color, dashes: es.dashes, arrows: es.arrows,
|
|
595
|
+
_label: e.label, _highlightColor: es._highlightColor || '#9ca3af',
|
|
596
|
+
_projectEdgeHidden: !!_chkIsProjectEdge, hidden: !!_chkIsProjectEdge,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Use incremental API (Phase-8C T8C.5)
|
|
602
|
+
if (network && network._gc) {
|
|
603
|
+
network._gc.addNodes(chunkNodes);
|
|
604
|
+
network._gc.addEdges(chunkEdges);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
chunkIndex++;
|
|
608
|
+
var pct = Math.min(100, Math.round(chunkIndex / totalChunks * 100));
|
|
609
|
+
log('加载进度: ' + pct + '% (' + (chunkIndex * CHUNK_SIZE) + '/' + sortedNodes.length + ')', true);
|
|
610
|
+
|
|
611
|
+
// Schedule next chunk (yield to main thread for rendering)
|
|
612
|
+
if (chunkIndex < totalChunks) {
|
|
613
|
+
setTimeout(loadNextChunk, 50);
|
|
614
|
+
} else {
|
|
615
|
+
log('✅ 全部数据加载完成: ' + Object.keys(loadedNodeIds).length + ' 节点', true);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Start loading remaining chunks after first render stabilizes
|
|
620
|
+
network.on('stabilizationIterationsDone', function() {
|
|
621
|
+
network.setOptions({ physics: { enabled: false } });
|
|
622
|
+
log('首批渲染稳定,开始后台增量加载...', true);
|
|
623
|
+
setTimeout(loadNextChunk, 100);
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
// Wire up click handler (same as renderGraph)
|
|
627
|
+
network.on('click', function(params) {
|
|
628
|
+
if (params.pointer && params.pointer.canvas) {
|
|
629
|
+
var hitNodeId = hitTestDocToggleBtn(params.pointer.canvas.x, params.pointer.canvas.y);
|
|
630
|
+
if (hitNodeId) { toggleDocNodeExpand(hitNodeId); return; }
|
|
631
|
+
}
|
|
632
|
+
if (params.nodes.length > 0) {
|
|
633
|
+
panelHistory = [];
|
|
634
|
+
currentPanelNodeId = null;
|
|
635
|
+
highlightConnectedEdges(params.nodes[0]);
|
|
636
|
+
showPanel(params.nodes[0]);
|
|
637
|
+
} else {
|
|
638
|
+
resetAllEdgeColors();
|
|
639
|
+
closePanel();
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
} catch (e) {
|
|
644
|
+
log('分块渲染失败: ' + e.message, false);
|
|
645
|
+
log('回退到标准渲染模式', true);
|
|
646
|
+
renderGraph();
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function renderStats(progress, graph) {
|
|
651
|
+
var bar = document.getElementById('statsBar');
|
|
652
|
+
var pct = progress.overallPercent || 0;
|
|
653
|
+
// 优先使用 /api/progress 返回的真实计数(分层加载时 graph.nodes 不含全部类型)
|
|
654
|
+
var moduleCount = progress.moduleCount;
|
|
655
|
+
var docCount = progress.docCount;
|
|
656
|
+
if (moduleCount == null || docCount == null) {
|
|
657
|
+
moduleCount = moduleCount || 0;
|
|
658
|
+
docCount = docCount || 0;
|
|
659
|
+
for (var i = 0; i < (graph.nodes || []).length; i++) {
|
|
660
|
+
if (graph.nodes[i].type === 'module') moduleCount++;
|
|
661
|
+
if (graph.nodes[i].type === 'document') docCount++;
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
var promptCount = progress.promptCount || 0;
|
|
665
|
+
bar.innerHTML =
|
|
666
|
+
'<div class="stat clickable" onclick="showStatsModal(\\x27module\\x27)" title="查看所有模块"><span class="num amber">' + moduleCount + '</span> 模块</div>' +
|
|
667
|
+
'<div class="stat clickable" onclick="showStatsModal(\\x27main-task\\x27)" title="查看所有主任务"><span class="num blue">' + progress.mainTaskCount + '</span> 主任务</div>' +
|
|
668
|
+
'<div class="stat clickable" onclick="showStatsModal(\\x27sub-task\\x27)" title="查看所有子任务"><span class="num purple">' + progress.subTaskCount + '</span> 子任务</div>' +
|
|
669
|
+
'<div class="stat clickable" onclick="showStatsModal(\\x27document\\x27)" title="查看所有文档"><span class="num" style="color:#3b82f6;">📄 ' + docCount + '</span> 文档</div>' +
|
|
670
|
+
'<div class="stat clickable" onclick="showPromptModal()" title="查看所有 Prompt"><span class="num" style="color:#ec4899;">💬 ' + promptCount + '</span> Prompt</div>' +
|
|
671
|
+
'<div class="stat"><span class="num green">' + progress.completedSubTasks + '/' + progress.subTaskCount + '</span> 已完成</div>' +
|
|
672
|
+
'<div class="stat"><div class="progress-bar"><div class="progress-fill" style="width:' + pct + '%"></div></div><span>' + pct + '%</span></div>';
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
`;
|
|
676
|
+
}
|
|
677
|
+
//# sourceMappingURL=template-data-loading.js.map
|