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.
Files changed (89) hide show
  1. package/dist/dev-plan-document-store.d.ts +13 -1
  2. package/dist/dev-plan-document-store.d.ts.map +1 -1
  3. package/dist/dev-plan-document-store.js +119 -0
  4. package/dist/dev-plan-document-store.js.map +1 -1
  5. package/dist/dev-plan-factory.d.ts.map +1 -1
  6. package/dist/dev-plan-factory.js +2 -1
  7. package/dist/dev-plan-factory.js.map +1 -1
  8. package/dist/dev-plan-graph-store.d.ts +64 -1
  9. package/dist/dev-plan-graph-store.d.ts.map +1 -1
  10. package/dist/dev-plan-graph-store.js +364 -2
  11. package/dist/dev-plan-graph-store.js.map +1 -1
  12. package/dist/dev-plan-interface.d.ts +24 -1
  13. package/dist/dev-plan-interface.d.ts.map +1 -1
  14. package/dist/dev-plan-migrate.d.ts +1 -0
  15. package/dist/dev-plan-migrate.d.ts.map +1 -1
  16. package/dist/dev-plan-migrate.js +28 -2
  17. package/dist/dev-plan-migrate.js.map +1 -1
  18. package/dist/index.d.ts +1 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp-server/index.js +119 -0
  22. package/dist/mcp-server/index.js.map +1 -1
  23. package/dist/types.d.ts +88 -1
  24. package/dist/types.d.ts.map +1 -1
  25. package/dist/types.js.map +1 -1
  26. package/dist/visualize/graph-canvas/api-compat.d.ts.map +1 -1
  27. package/dist/visualize/graph-canvas/api-compat.js +22 -12
  28. package/dist/visualize/graph-canvas/api-compat.js.map +1 -1
  29. package/dist/visualize/graph-canvas/core.d.ts.map +1 -1
  30. package/dist/visualize/graph-canvas/core.js +296 -4
  31. package/dist/visualize/graph-canvas/core.js.map +1 -1
  32. package/dist/visualize/graph-canvas/interaction.d.ts.map +1 -1
  33. package/dist/visualize/graph-canvas/interaction.js +11 -0
  34. package/dist/visualize/graph-canvas/interaction.js.map +1 -1
  35. package/dist/visualize/graph-canvas/layout-worker.d.ts.map +1 -1
  36. package/dist/visualize/graph-canvas/layout-worker.js +45 -9
  37. package/dist/visualize/graph-canvas/layout-worker.js.map +1 -1
  38. package/dist/visualize/graph-canvas/renderer.d.ts.map +1 -1
  39. package/dist/visualize/graph-canvas/renderer.js +164 -33
  40. package/dist/visualize/graph-canvas/renderer.js.map +1 -1
  41. package/dist/visualize/graph-canvas/styles.d.ts.map +1 -1
  42. package/dist/visualize/graph-canvas/styles.js +136 -121
  43. package/dist/visualize/graph-canvas/styles.js.map +1 -1
  44. package/dist/visualize/graph-canvas/viewport.d.ts.map +1 -1
  45. package/dist/visualize/graph-canvas/viewport.js +10 -0
  46. package/dist/visualize/graph-canvas/viewport.js.map +1 -1
  47. package/dist/visualize/server.js +149 -32
  48. package/dist/visualize/server.js.map +1 -1
  49. package/dist/visualize/template-core.d.ts +9 -0
  50. package/dist/visualize/template-core.d.ts.map +1 -0
  51. package/dist/visualize/template-core.js +714 -0
  52. package/dist/visualize/template-core.js.map +1 -0
  53. package/dist/visualize/template-data-loading.d.ts +7 -0
  54. package/dist/visualize/template-data-loading.d.ts.map +1 -0
  55. package/dist/visualize/template-data-loading.js +677 -0
  56. package/dist/visualize/template-data-loading.js.map +1 -0
  57. package/dist/visualize/template-detail-panel.d.ts +14 -0
  58. package/dist/visualize/template-detail-panel.d.ts.map +1 -0
  59. package/dist/visualize/template-detail-panel.js +553 -0
  60. package/dist/visualize/template-detail-panel.js.map +1 -0
  61. package/dist/visualize/template-graph-3d.d.ts +7 -0
  62. package/dist/visualize/template-graph-3d.d.ts.map +1 -0
  63. package/dist/visualize/template-graph-3d.js +1112 -0
  64. package/dist/visualize/template-graph-3d.js.map +1 -0
  65. package/dist/visualize/template-graph-vis.d.ts +8 -0
  66. package/dist/visualize/template-graph-vis.d.ts.map +1 -0
  67. package/dist/visualize/template-graph-vis.js +1204 -0
  68. package/dist/visualize/template-graph-vis.js.map +1 -0
  69. package/dist/visualize/template-html.d.ts +9 -0
  70. package/dist/visualize/template-html.d.ts.map +1 -0
  71. package/dist/visualize/template-html.js +484 -0
  72. package/dist/visualize/template-html.js.map +1 -0
  73. package/dist/visualize/template-pages.d.ts +7 -0
  74. package/dist/visualize/template-pages.d.ts.map +1 -0
  75. package/dist/visualize/template-pages.js +806 -0
  76. package/dist/visualize/template-pages.js.map +1 -0
  77. package/dist/visualize/template-stats-modal.d.ts +7 -0
  78. package/dist/visualize/template-stats-modal.d.ts.map +1 -0
  79. package/dist/visualize/template-stats-modal.js +406 -0
  80. package/dist/visualize/template-stats-modal.js.map +1 -0
  81. package/dist/visualize/template-styles.d.ts +9 -0
  82. package/dist/visualize/template-styles.d.ts.map +1 -0
  83. package/dist/visualize/template-styles.js +487 -0
  84. package/dist/visualize/template-styles.js.map +1 -0
  85. package/dist/visualize/template.d.ts +14 -3
  86. package/dist/visualize/template.d.ts.map +1 -1
  87. package/dist/visualize/template.js +38 -3475
  88. package/dist/visualize/template.js.map +1 -1
  89. 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,7 @@
1
+ /**
2
+ * DevPlan 图可视化 — 数据加载模块
3
+ *
4
+ * 包含: API 数据获取、分层加载、概览模式、节点/边数据处理。
5
+ */
6
+ export declare function getDataLoadingScript(): string;
7
+ //# sourceMappingURL=template-data-loading.d.ts.map
@@ -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