aifastdb-devplan 1.6.1 → 1.6.3
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 +3 -1
- package/dist/dev-plan-factory.js.map +1 -1
- package/dist/dev-plan-graph-store.d.ts +341 -9
- package/dist/dev-plan-graph-store.d.ts.map +1 -1
- package/dist/dev-plan-graph-store.js +2414 -210
- package/dist/dev-plan-graph-store.js.map +1 -1
- package/dist/dev-plan-interface.d.ts +119 -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 +652 -0
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/shard-config.d.ts +64 -0
- package/dist/shard-config.d.ts.map +1 -0
- package/dist/shard-config.js +109 -0
- package/dist/shard-config.js.map +1 -0
- package/dist/types.d.ts +305 -2
- 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 +146 -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 +371 -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 +721 -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 +624 -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 +1114 -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 +1215 -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 +635 -0
- package/dist/visualize/template-html.js.map +1 -0
- package/dist/visualize/template-md-viewer.d.ts +11 -0
- package/dist/visualize/template-md-viewer.d.ts.map +1 -0
- package/dist/visualize/template-md-viewer.js +806 -0
- package/dist/visualize/template-md-viewer.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 +1892 -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 +466 -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 +623 -0
- package/dist/visualize/template-styles.js.map +1 -0
- package/dist/visualize/template.d.ts +15 -3
- package/dist/visualize/template.d.ts.map +1 -1
- package/dist/visualize/template.js +44 -3475
- package/dist/visualize/template.js.map +1 -1
- package/package.json +2 -2
package/dist/visualize/server.js
CHANGED
|
@@ -276,12 +276,15 @@ function startServer(projectName, basePath, port) {
|
|
|
276
276
|
const includeModules = url.searchParams.get('includeModules') !== 'false';
|
|
277
277
|
const includeNodeDegree = url.searchParams.get('includeNodeDegree') !== 'false';
|
|
278
278
|
const enableBackendDegreeFallback = url.searchParams.get('enableBackendDegreeFallback') !== 'false';
|
|
279
|
+
// 可视化页面默认不渲染 Prompt 节点(通过顶部统计栏点击查看 Prompt 列表)
|
|
280
|
+
const includePrompts = url.searchParams.get('includePrompts') === 'true';
|
|
279
281
|
if (store.exportGraph) {
|
|
280
282
|
const graph = store.exportGraph({
|
|
281
283
|
includeDocuments,
|
|
282
284
|
includeModules,
|
|
283
285
|
includeNodeDegree,
|
|
284
286
|
enableBackendDegreeFallback,
|
|
287
|
+
includePrompts,
|
|
285
288
|
});
|
|
286
289
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
287
290
|
res.end(JSON.stringify(graph));
|
|
@@ -296,8 +299,40 @@ function startServer(projectName, basePath, port) {
|
|
|
296
299
|
// 每次请求重新创建 store,确保读取最新数据
|
|
297
300
|
const store = createFreshStore(projectName, basePath);
|
|
298
301
|
const progress = store.getProgress();
|
|
302
|
+
// 附加模块和文档计数(分层加载模式下 graph.nodes 不含全部类型,需从此处获取真实数量)
|
|
303
|
+
const sections = store.listSections();
|
|
304
|
+
const modules = store.listModules();
|
|
305
|
+
// 附加 Prompt 计数
|
|
306
|
+
let promptCount = 0;
|
|
307
|
+
try {
|
|
308
|
+
if (typeof store.listPrompts === 'function') {
|
|
309
|
+
promptCount = store.listPrompts().length;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
catch (e) { /* listPrompts 不支持时忽略 */ }
|
|
313
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
314
|
+
res.end(JSON.stringify({
|
|
315
|
+
...progress,
|
|
316
|
+
moduleCount: modules.length,
|
|
317
|
+
docCount: sections.length,
|
|
318
|
+
promptCount,
|
|
319
|
+
}));
|
|
320
|
+
break;
|
|
321
|
+
}
|
|
322
|
+
case '/api/prompts': {
|
|
323
|
+
// 列出所有 Prompt 日志
|
|
324
|
+
const store = createFreshStore(projectName, basePath);
|
|
325
|
+
let prompts = [];
|
|
326
|
+
try {
|
|
327
|
+
if (typeof store.listPrompts === 'function') {
|
|
328
|
+
prompts = store.listPrompts();
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (e) {
|
|
332
|
+
prompts = [];
|
|
333
|
+
}
|
|
299
334
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
300
|
-
res.end(JSON.stringify(
|
|
335
|
+
res.end(JSON.stringify({ prompts, count: prompts.length }));
|
|
301
336
|
break;
|
|
302
337
|
}
|
|
303
338
|
case '/api/stats': {
|
|
@@ -400,6 +435,228 @@ function startServer(projectName, basePath, port) {
|
|
|
400
435
|
res.end(JSON.stringify(stats));
|
|
401
436
|
break;
|
|
402
437
|
}
|
|
438
|
+
case '/api/memories': {
|
|
439
|
+
// 列出所有记忆(用于记忆浏览页面)
|
|
440
|
+
const memStore = createFreshStore(projectName, basePath);
|
|
441
|
+
let memories = [];
|
|
442
|
+
if (typeof memStore.listMemories === 'function') {
|
|
443
|
+
const memoryType = url.searchParams.get('memoryType') || undefined;
|
|
444
|
+
memories = memStore.listMemories({
|
|
445
|
+
memoryType,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
449
|
+
res.end(JSON.stringify({ memories }));
|
|
450
|
+
break;
|
|
451
|
+
}
|
|
452
|
+
case '/api/memories/generate': {
|
|
453
|
+
// 从文档/任务中生成记忆候选项
|
|
454
|
+
const genStore = createFreshStore(projectName, basePath);
|
|
455
|
+
if (typeof genStore.generateMemoryCandidates !== 'function') {
|
|
456
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
457
|
+
res.end(JSON.stringify({ error: 'generateMemoryCandidates not supported (requires graph engine)' }));
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
const genSource = url.searchParams.get('source') || 'both';
|
|
461
|
+
const genTaskId = url.searchParams.get('taskId') || undefined;
|
|
462
|
+
const genSection = url.searchParams.get('section') || undefined;
|
|
463
|
+
const rawLimit = url.searchParams.get('limit') ? parseInt(url.searchParams.get('limit'), 10) : 50;
|
|
464
|
+
const genLimit = rawLimit <= 0 ? 99999 : rawLimit;
|
|
465
|
+
const result = genStore.generateMemoryCandidates({
|
|
466
|
+
source: genSource,
|
|
467
|
+
taskId: genTaskId,
|
|
468
|
+
section: genSection,
|
|
469
|
+
limit: genLimit,
|
|
470
|
+
});
|
|
471
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
472
|
+
res.end(JSON.stringify(result));
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
case '/api/memories/save': {
|
|
476
|
+
// 保存一条记忆(POST)
|
|
477
|
+
if (req.method !== 'POST') {
|
|
478
|
+
res.writeHead(405, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
479
|
+
res.end(JSON.stringify({ error: 'Method Not Allowed. Use POST.' }));
|
|
480
|
+
break;
|
|
481
|
+
}
|
|
482
|
+
const saveBody = await readRequestBody(req);
|
|
483
|
+
const saveStore = createFreshStore(projectName, basePath);
|
|
484
|
+
if (typeof saveStore.saveMemory !== 'function') {
|
|
485
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
486
|
+
res.end(JSON.stringify({ error: 'saveMemory not supported (requires graph engine)' }));
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
try {
|
|
490
|
+
const saved = saveStore.saveMemory({
|
|
491
|
+
projectName,
|
|
492
|
+
memoryType: saveBody.memoryType || 'insight',
|
|
493
|
+
content: saveBody.content || '',
|
|
494
|
+
tags: saveBody.tags || [],
|
|
495
|
+
relatedTaskId: saveBody.relatedTaskId || undefined,
|
|
496
|
+
sourceId: saveBody.sourceId || undefined,
|
|
497
|
+
importance: saveBody.importance ?? 0.5,
|
|
498
|
+
});
|
|
499
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
500
|
+
res.end(JSON.stringify({ status: 'saved', memory: saved }));
|
|
501
|
+
}
|
|
502
|
+
catch (e) {
|
|
503
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
504
|
+
res.end(JSON.stringify({ error: e.message || String(e) }));
|
|
505
|
+
}
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
case '/api/memories/relate': {
|
|
509
|
+
// Phase-44: 建立记忆间关系(POST)
|
|
510
|
+
if (req.method !== 'POST') {
|
|
511
|
+
res.writeHead(405, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
512
|
+
res.end(JSON.stringify({ error: 'Method Not Allowed. Use POST.' }));
|
|
513
|
+
break;
|
|
514
|
+
}
|
|
515
|
+
const relBody = await readRequestBody(req);
|
|
516
|
+
const relStore = createFreshStore(projectName, basePath);
|
|
517
|
+
try {
|
|
518
|
+
// 使用 graph 的 put_relation 或 applyMutations 建立关系
|
|
519
|
+
const graph = relStore.graph;
|
|
520
|
+
if (!graph) {
|
|
521
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
522
|
+
res.end(JSON.stringify({ error: 'Graph engine not available' }));
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
const fromId = relBody.fromId;
|
|
526
|
+
const toId = relBody.toId;
|
|
527
|
+
const relationType = relBody.relationType || 'MEMORY_RELATES';
|
|
528
|
+
const weight = relBody.weight ?? 0.5;
|
|
529
|
+
// 尝试使用 applyMutations (Phase-44),回退到 putRelation
|
|
530
|
+
if (typeof graph.applyMutations === 'function') {
|
|
531
|
+
graph.applyMutations([{
|
|
532
|
+
type: 'PutRelation',
|
|
533
|
+
relation: {
|
|
534
|
+
source_id: fromId,
|
|
535
|
+
target_id: toId,
|
|
536
|
+
relation_type: relationType,
|
|
537
|
+
weight: weight,
|
|
538
|
+
}
|
|
539
|
+
}]);
|
|
540
|
+
}
|
|
541
|
+
else if (typeof graph.putRelation === 'function') {
|
|
542
|
+
graph.putRelation({
|
|
543
|
+
source_id: fromId,
|
|
544
|
+
target_id: toId,
|
|
545
|
+
relation_type: relationType,
|
|
546
|
+
weight: weight,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
else {
|
|
550
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
551
|
+
res.end(JSON.stringify({ error: 'No relation creation method available' }));
|
|
552
|
+
break;
|
|
553
|
+
}
|
|
554
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
555
|
+
res.end(JSON.stringify({ status: 'created', from: fromId, to: toId, relationType, weight }));
|
|
556
|
+
}
|
|
557
|
+
catch (e) {
|
|
558
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
559
|
+
res.end(JSON.stringify({ error: e.message || String(e) }));
|
|
560
|
+
}
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
case '/api/memories/clear': {
|
|
564
|
+
// 批量清除所有记忆(POST)
|
|
565
|
+
if (req.method !== 'POST') {
|
|
566
|
+
res.writeHead(405, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
567
|
+
res.end(JSON.stringify({ error: 'Method Not Allowed. Use POST.' }));
|
|
568
|
+
break;
|
|
569
|
+
}
|
|
570
|
+
const clearBody = await readRequestBody(req);
|
|
571
|
+
if (clearBody.confirm !== true) {
|
|
572
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
573
|
+
res.end(JSON.stringify({ error: 'Safety guard: confirm must be true' }));
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
const clearStore = createFreshStore(projectName, basePath);
|
|
577
|
+
if (typeof clearStore.clearAllMemories !== 'function') {
|
|
578
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
579
|
+
res.end(JSON.stringify({ error: 'clearAllMemories not supported (requires graph engine)' }));
|
|
580
|
+
break;
|
|
581
|
+
}
|
|
582
|
+
try {
|
|
583
|
+
const clearResult = clearStore.clearAllMemories(clearBody.memoryType || undefined);
|
|
584
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
585
|
+
res.end(JSON.stringify({ status: 'cleared', ...clearResult, memoryType: clearBody.memoryType || 'all' }));
|
|
586
|
+
}
|
|
587
|
+
catch (e) {
|
|
588
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
589
|
+
res.end(JSON.stringify({ error: e.message || String(e) }));
|
|
590
|
+
}
|
|
591
|
+
break;
|
|
592
|
+
}
|
|
593
|
+
case '/api/memories/graph': {
|
|
594
|
+
// 导出仅记忆相关的图数据(用于记忆页面 3D 可视化)
|
|
595
|
+
const memGraphStore = createFreshStore(projectName, basePath);
|
|
596
|
+
if (!memGraphStore.exportGraph) {
|
|
597
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
598
|
+
res.end(JSON.stringify({ error: 'exportGraph not supported (requires graph engine)' }));
|
|
599
|
+
break;
|
|
600
|
+
}
|
|
601
|
+
try {
|
|
602
|
+
// 导出完整图(含记忆),然后过滤出记忆相关节点和边
|
|
603
|
+
const fullGraph = memGraphStore.exportGraph({
|
|
604
|
+
includeDocuments: true,
|
|
605
|
+
includeModules: true,
|
|
606
|
+
includeNodeDegree: false,
|
|
607
|
+
enableBackendDegreeFallback: false,
|
|
608
|
+
includePrompts: false,
|
|
609
|
+
});
|
|
610
|
+
if (!fullGraph) {
|
|
611
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
612
|
+
res.end(JSON.stringify({ error: 'exportGraph returned null' }));
|
|
613
|
+
break;
|
|
614
|
+
}
|
|
615
|
+
// 收集记忆节点 ID
|
|
616
|
+
const memoryNodeIds = new Set();
|
|
617
|
+
const memoryNodes = [];
|
|
618
|
+
const contextNodes = [];
|
|
619
|
+
const contextNodeIds = new Set();
|
|
620
|
+
for (const node of fullGraph.nodes) {
|
|
621
|
+
if (node.type === 'memory') {
|
|
622
|
+
memoryNodeIds.add(node.id);
|
|
623
|
+
memoryNodes.push(node);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
// 收集与记忆相关的边和上下文节点(任务、文档、模块)
|
|
627
|
+
const memEdges = [];
|
|
628
|
+
for (const edge of fullGraph.edges) {
|
|
629
|
+
const fromIsMem = memoryNodeIds.has(edge.from);
|
|
630
|
+
const toIsMem = memoryNodeIds.has(edge.to);
|
|
631
|
+
if (fromIsMem || toIsMem) {
|
|
632
|
+
memEdges.push(edge);
|
|
633
|
+
// 添加非记忆端的上下文节点
|
|
634
|
+
const otherId = fromIsMem ? edge.to : edge.from;
|
|
635
|
+
if (!memoryNodeIds.has(otherId) && !contextNodeIds.has(otherId)) {
|
|
636
|
+
contextNodeIds.add(otherId);
|
|
637
|
+
const ctxNode = fullGraph.nodes.find((n) => n.id === otherId);
|
|
638
|
+
if (ctxNode)
|
|
639
|
+
contextNodes.push(ctxNode);
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
644
|
+
res.end(JSON.stringify({
|
|
645
|
+
nodes: [...memoryNodes, ...contextNodes],
|
|
646
|
+
edges: memEdges,
|
|
647
|
+
stats: {
|
|
648
|
+
memoryCount: memoryNodes.length,
|
|
649
|
+
contextCount: contextNodes.length,
|
|
650
|
+
edgeCount: memEdges.length,
|
|
651
|
+
},
|
|
652
|
+
}));
|
|
653
|
+
}
|
|
654
|
+
catch (e) {
|
|
655
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
656
|
+
res.end(JSON.stringify({ error: e.message || String(e) }));
|
|
657
|
+
}
|
|
658
|
+
break;
|
|
659
|
+
}
|
|
403
660
|
case '/api/docs': {
|
|
404
661
|
// 列出所有文档片段(不含内容,用于文档浏览页面左侧列表)
|
|
405
662
|
const store = createFreshStore(projectName, basePath);
|
|
@@ -855,19 +1112,48 @@ function startServer(projectName, basePath, port) {
|
|
|
855
1112
|
break;
|
|
856
1113
|
}
|
|
857
1114
|
case '/api/graph/paged': {
|
|
858
|
-
// ── Phase-
|
|
859
|
-
// GET /api/graph/paged?offset=0&limit=5000
|
|
860
|
-
// Returns a page of { nodes, edges,
|
|
861
|
-
//
|
|
862
|
-
//
|
|
863
|
-
const
|
|
1115
|
+
// ── Phase-9 T9.2: True pagination push-down to Rust layer ──
|
|
1116
|
+
// GET /api/graph/paged?offset=0&limit=5000
|
|
1117
|
+
// Returns a page of { nodes, edges, totalNodes, totalEdges, hasMore }
|
|
1118
|
+
// Now calls Rust SocialGraphV2.exportGraphPaginated() via NAPI
|
|
1119
|
+
// instead of full-loading + in-memory slicing.
|
|
1120
|
+
const pagedStore = createFreshStore(projectName, basePath);
|
|
864
1121
|
const offset = parseInt(url.searchParams.get('offset') || '0', 10);
|
|
865
1122
|
const limit = parseInt(url.searchParams.get('limit') || '5000', 10);
|
|
866
|
-
const sortBy = url.searchParams.get('sortBy') || 'default';
|
|
867
1123
|
const includeDocuments = url.searchParams.get('includeDocuments') !== 'false';
|
|
868
1124
|
const includeModules = url.searchParams.get('includeModules') !== 'false';
|
|
869
|
-
|
|
870
|
-
|
|
1125
|
+
// Phase-10 T10.1: Support entityTypes query param for tiered loading
|
|
1126
|
+
const entityTypesParam = url.searchParams.get('entityTypes');
|
|
1127
|
+
const entityTypes = entityTypesParam ? entityTypesParam.split(',').map(t => t.trim()).filter(Boolean) : undefined;
|
|
1128
|
+
// Use the new pagination push-down method if available
|
|
1129
|
+
if (pagedStore.exportGraphPaginated) {
|
|
1130
|
+
try {
|
|
1131
|
+
const result = pagedStore.exportGraphPaginated(offset, limit, {
|
|
1132
|
+
includeDocuments,
|
|
1133
|
+
includeModules,
|
|
1134
|
+
includeNodeDegree: true,
|
|
1135
|
+
entityTypes,
|
|
1136
|
+
});
|
|
1137
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1138
|
+
res.end(JSON.stringify({
|
|
1139
|
+
nodes: result.nodes,
|
|
1140
|
+
edges: result.edges,
|
|
1141
|
+
total: result.totalNodes,
|
|
1142
|
+
totalEdges: result.totalEdges,
|
|
1143
|
+
offset: result.offset,
|
|
1144
|
+
limit: result.limit,
|
|
1145
|
+
hasMore: result.hasMore,
|
|
1146
|
+
nextOffset: Math.min(offset + limit, result.totalNodes),
|
|
1147
|
+
}));
|
|
1148
|
+
}
|
|
1149
|
+
catch (err) {
|
|
1150
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1151
|
+
res.end(JSON.stringify({ error: `分页导出失败: ${err?.message || err}` }));
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
else if (pagedStore.exportGraph) {
|
|
1155
|
+
// Fallback: full load + in-memory slicing (pre-Phase-9 behavior)
|
|
1156
|
+
const fullGraph = pagedStore.exportGraph({
|
|
871
1157
|
includeDocuments,
|
|
872
1158
|
includeModules,
|
|
873
1159
|
includeNodeDegree: true,
|
|
@@ -878,31 +1164,10 @@ function startServer(projectName, basePath, port) {
|
|
|
878
1164
|
res.end(JSON.stringify({ error: '图数据导出失败' }));
|
|
879
1165
|
break;
|
|
880
1166
|
}
|
|
881
|
-
|
|
1167
|
+
const allNodes = fullGraph.nodes || [];
|
|
882
1168
|
const allEdges = fullGraph.edges || [];
|
|
883
|
-
// T8C.4: Viewport-priority sorting — sort by distance from center
|
|
884
|
-
if (sortBy === 'center') {
|
|
885
|
-
// Calculate centroid of all nodes
|
|
886
|
-
let cx = 0, cy = 0;
|
|
887
|
-
for (const n of allNodes) {
|
|
888
|
-
cx += (n.x || 0);
|
|
889
|
-
cy += (n.y || 0);
|
|
890
|
-
}
|
|
891
|
-
if (allNodes.length > 0) {
|
|
892
|
-
cx /= allNodes.length;
|
|
893
|
-
cy /= allNodes.length;
|
|
894
|
-
}
|
|
895
|
-
// Sort by distance to centroid (closest first)
|
|
896
|
-
allNodes = [...allNodes].sort((a, b) => {
|
|
897
|
-
const da = (a.x - cx) ** 2 + (a.y - cy) ** 2;
|
|
898
|
-
const db = (b.x - cx) ** 2 + (b.y - cy) ** 2;
|
|
899
|
-
return da - db;
|
|
900
|
-
});
|
|
901
|
-
}
|
|
902
|
-
// Slice the page
|
|
903
1169
|
const pageNodes = allNodes.slice(offset, offset + limit);
|
|
904
1170
|
const pageNodeIds = new Set(pageNodes.map((n) => n.id));
|
|
905
|
-
// Include only edges where both endpoints are in this page
|
|
906
1171
|
const pageEdges = allEdges.filter((e) => pageNodeIds.has(e.from) && pageNodeIds.has(e.to));
|
|
907
1172
|
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
908
1173
|
res.end(JSON.stringify({
|
|
@@ -922,6 +1187,80 @@ function startServer(projectName, basePath, port) {
|
|
|
922
1187
|
}
|
|
923
1188
|
break;
|
|
924
1189
|
}
|
|
1190
|
+
case '/api/graph/binary': {
|
|
1191
|
+
// ── Phase-9 T9.3: Binary compact export endpoint ──
|
|
1192
|
+
// GET /api/graph/binary
|
|
1193
|
+
// Returns ArrayBuffer with compact binary format (5x smaller than JSON)
|
|
1194
|
+
// Client can parse directly as TypedArray, no JSON.parse needed.
|
|
1195
|
+
const binaryStore = createFreshStore(projectName, basePath);
|
|
1196
|
+
if (binaryStore.exportGraphCompact) {
|
|
1197
|
+
try {
|
|
1198
|
+
const buf = binaryStore.exportGraphCompact();
|
|
1199
|
+
if (buf && buf.length > 0) {
|
|
1200
|
+
res.writeHead(200, {
|
|
1201
|
+
'Content-Type': 'application/octet-stream',
|
|
1202
|
+
'Content-Length': String(buf.length),
|
|
1203
|
+
'X-Node-Count': String(new DataView(buf.buffer, buf.byteOffset, buf.byteLength).getUint32(8, true)),
|
|
1204
|
+
'X-Edge-Count': String(new DataView(buf.buffer, buf.byteOffset, buf.byteLength).getUint32(12, true)),
|
|
1205
|
+
});
|
|
1206
|
+
res.end(buf);
|
|
1207
|
+
}
|
|
1208
|
+
else {
|
|
1209
|
+
// Fallback: return empty binary
|
|
1210
|
+
const emptyBuf = Buffer.alloc(16);
|
|
1211
|
+
emptyBuf.writeUInt32LE(0x41494647, 0); // magic
|
|
1212
|
+
emptyBuf.writeUInt32LE(1, 4); // version
|
|
1213
|
+
emptyBuf.writeUInt32LE(0, 8); // node_count
|
|
1214
|
+
emptyBuf.writeUInt32LE(0, 12); // edge_count
|
|
1215
|
+
res.writeHead(200, {
|
|
1216
|
+
'Content-Type': 'application/octet-stream',
|
|
1217
|
+
'Content-Length': '16',
|
|
1218
|
+
'X-Node-Count': '0',
|
|
1219
|
+
'X-Edge-Count': '0',
|
|
1220
|
+
});
|
|
1221
|
+
res.end(emptyBuf);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
catch (err) {
|
|
1225
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1226
|
+
res.end(JSON.stringify({ error: `二进制导出失败: ${err?.message || err}` }));
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1231
|
+
res.end(JSON.stringify({ error: '当前引擎不支持二进制导出' }));
|
|
1232
|
+
}
|
|
1233
|
+
break;
|
|
1234
|
+
}
|
|
1235
|
+
case '/api/graph/clusters': {
|
|
1236
|
+
// ── Phase-9 T9.4: Server-side aggregation endpoint ──
|
|
1237
|
+
// GET /api/graph/clusters
|
|
1238
|
+
// Returns pre-aggregated entity group summaries.
|
|
1239
|
+
// Ideal for low-zoom cluster views — no need to transfer all nodes.
|
|
1240
|
+
const clusterStore = createFreshStore(projectName, basePath);
|
|
1241
|
+
if (clusterStore.getEntityGroupSummary) {
|
|
1242
|
+
try {
|
|
1243
|
+
const agg = clusterStore.getEntityGroupSummary();
|
|
1244
|
+
if (agg) {
|
|
1245
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1246
|
+
res.end(JSON.stringify(agg));
|
|
1247
|
+
}
|
|
1248
|
+
else {
|
|
1249
|
+
res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1250
|
+
res.end(JSON.stringify({ groups: [], totalEntities: 0, totalRelations: 0 }));
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
catch (err) {
|
|
1254
|
+
res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1255
|
+
res.end(JSON.stringify({ error: `聚合查询失败: ${err?.message || err}` }));
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
else {
|
|
1259
|
+
res.writeHead(400, { 'Content-Type': 'application/json; charset=utf-8' });
|
|
1260
|
+
res.end(JSON.stringify({ error: '当前引擎不支持聚合查询' }));
|
|
1261
|
+
}
|
|
1262
|
+
break;
|
|
1263
|
+
}
|
|
925
1264
|
case '/graph-canvas.js': {
|
|
926
1265
|
// Serve the GraphCanvas engine as a JavaScript file
|
|
927
1266
|
res.writeHead(200, {
|