n8n-nodes-engram 0.2.3 → 0.2.5
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/README.md +3 -3
- package/dist/extraction/ContradictionDetector.d.ts +1 -1
- package/dist/extraction/ContradictionDetector.js +2 -2
- package/dist/extraction/ContradictionDetector.js.map +1 -1
- package/dist/extraction/EdgeDeduplicator.d.ts +13 -0
- package/dist/extraction/EdgeDeduplicator.js +52 -0
- package/dist/extraction/EdgeDeduplicator.js.map +1 -0
- package/dist/extraction/ExtractionPipeline.d.ts +3 -0
- package/dist/extraction/ExtractionPipeline.js +89 -9
- package/dist/extraction/ExtractionPipeline.js.map +1 -1
- package/dist/extraction/prompts.d.ts +5 -1
- package/dist/extraction/prompts.js +75 -9
- package/dist/extraction/prompts.js.map +1 -1
- package/dist/nodes/EngramAdmin/EngramAdmin.node.js +511 -399
- package/dist/nodes/EngramAdmin/EngramAdmin.node.js.map +1 -1
- package/dist/nodes/EngramExplorer/EngramExplorer.node.js +11 -11
- package/dist/nodes/EngramExplorer/EngramExplorer.node.js.map +1 -1
- package/dist/utils/helpers.js +1 -1
- package/dist/utils/helpers.js.map +1 -1
- package/package.json +4 -6
|
@@ -14,7 +14,7 @@ class EngramAdmin {
|
|
|
14
14
|
name: 'engramAdmin',
|
|
15
15
|
icon: 'file:engram-admin.png',
|
|
16
16
|
group: ['transform'],
|
|
17
|
-
version: 1,
|
|
17
|
+
version: [1, 2],
|
|
18
18
|
description: 'Manage and administer the Engram knowledge graph',
|
|
19
19
|
defaults: {
|
|
20
20
|
name: 'Engram Admin',
|
|
@@ -52,79 +52,167 @@ class EngramAdmin {
|
|
|
52
52
|
],
|
|
53
53
|
default: 'embedded',
|
|
54
54
|
},
|
|
55
|
+
{
|
|
56
|
+
displayName: 'Resource',
|
|
57
|
+
name: 'resource',
|
|
58
|
+
type: 'options',
|
|
59
|
+
noDataExpression: true,
|
|
60
|
+
options: [
|
|
61
|
+
{
|
|
62
|
+
name: 'Monitoring',
|
|
63
|
+
value: 'monitoring',
|
|
64
|
+
description: 'Graph statistics and group monitoring',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
name: 'Lifecycle',
|
|
68
|
+
value: 'lifecycle',
|
|
69
|
+
description: 'Data retention, clearing groups, and cleanup',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Hygiene',
|
|
73
|
+
value: 'hygiene',
|
|
74
|
+
description: 'Find orphaned entities, duplicates, and stale edges',
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'Portability',
|
|
78
|
+
value: 'portability',
|
|
79
|
+
description: 'Export and import graph data',
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
name: 'Analysis',
|
|
83
|
+
value: 'analysis',
|
|
84
|
+
description: 'Community detection and graph analysis',
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
default: 'monitoring',
|
|
88
|
+
},
|
|
55
89
|
{
|
|
56
90
|
displayName: 'Operation',
|
|
57
91
|
name: 'operation',
|
|
58
92
|
type: 'options',
|
|
59
93
|
noDataExpression: true,
|
|
94
|
+
displayOptions: { show: { resource: ['monitoring'] } },
|
|
60
95
|
options: [
|
|
61
96
|
{
|
|
62
97
|
name: 'Stats',
|
|
63
98
|
value: 'stats',
|
|
64
99
|
description: 'Get graph statistics with enhanced metrics',
|
|
100
|
+
action: 'Get graph statistics',
|
|
65
101
|
},
|
|
66
102
|
{
|
|
67
103
|
name: 'List Groups',
|
|
68
104
|
value: 'listGroups',
|
|
69
105
|
description: 'List all groups/sessions with per-group statistics',
|
|
106
|
+
action: 'List all groups',
|
|
70
107
|
},
|
|
71
108
|
{
|
|
72
109
|
name: 'Group Stats',
|
|
73
110
|
value: 'groupStats',
|
|
74
111
|
description: 'Get detailed statistics for a specific group/session',
|
|
112
|
+
action: 'Get group statistics',
|
|
75
113
|
},
|
|
114
|
+
],
|
|
115
|
+
default: 'stats',
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
displayName: 'Operation',
|
|
119
|
+
name: 'operation',
|
|
120
|
+
type: 'options',
|
|
121
|
+
noDataExpression: true,
|
|
122
|
+
displayOptions: { show: { resource: ['lifecycle'] } },
|
|
123
|
+
options: [
|
|
76
124
|
{
|
|
77
125
|
name: 'Apply Retention',
|
|
78
126
|
value: 'applyRetention',
|
|
79
127
|
description: 'Remove old episodes based on a retention policy',
|
|
128
|
+
action: 'Apply retention policy',
|
|
80
129
|
},
|
|
81
130
|
{
|
|
82
131
|
name: 'Clear Group',
|
|
83
132
|
value: 'clearGroup',
|
|
84
133
|
description: 'Clear all data for a specific group/session',
|
|
134
|
+
action: 'Clear a group',
|
|
85
135
|
},
|
|
86
136
|
{
|
|
87
137
|
name: 'Bulk Clear Groups',
|
|
88
138
|
value: 'bulkClearGroups',
|
|
89
139
|
description: 'Clear multiple groups/sessions at once',
|
|
140
|
+
action: 'Bulk clear groups',
|
|
90
141
|
},
|
|
91
142
|
{
|
|
92
143
|
name: 'Clear All',
|
|
93
144
|
value: 'clearAll',
|
|
94
145
|
description: 'Clear ALL data from the graph (destructive!)',
|
|
146
|
+
action: 'Clear all data',
|
|
95
147
|
},
|
|
148
|
+
],
|
|
149
|
+
default: 'applyRetention',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
displayName: 'Operation',
|
|
153
|
+
name: 'operation',
|
|
154
|
+
type: 'options',
|
|
155
|
+
noDataExpression: true,
|
|
156
|
+
displayOptions: { show: { resource: ['hygiene'] } },
|
|
157
|
+
options: [
|
|
96
158
|
{
|
|
97
159
|
name: 'Orphaned Entities',
|
|
98
160
|
value: 'orphanedEntities',
|
|
99
161
|
description: 'Find or remove entities with no relationships',
|
|
162
|
+
action: 'Find orphaned entities',
|
|
100
163
|
},
|
|
101
164
|
{
|
|
102
165
|
name: 'Duplicate Entities',
|
|
103
166
|
value: 'duplicateEntities',
|
|
104
167
|
description: 'Find entities with duplicate or similar names',
|
|
168
|
+
action: 'Find duplicate entities',
|
|
105
169
|
},
|
|
106
170
|
{
|
|
107
171
|
name: 'Expire Stale Edges',
|
|
108
172
|
value: 'expireStaleEdges',
|
|
109
173
|
description: 'Find and expire edges with broken references or past validity',
|
|
174
|
+
action: 'Expire stale edges',
|
|
110
175
|
},
|
|
176
|
+
],
|
|
177
|
+
default: 'orphanedEntities',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
displayName: 'Operation',
|
|
181
|
+
name: 'operation',
|
|
182
|
+
type: 'options',
|
|
183
|
+
noDataExpression: true,
|
|
184
|
+
displayOptions: { show: { resource: ['portability'] } },
|
|
185
|
+
options: [
|
|
111
186
|
{
|
|
112
187
|
name: 'Export',
|
|
113
188
|
value: 'export',
|
|
114
189
|
description: 'Export graph data as JSON',
|
|
190
|
+
action: 'Export graph data',
|
|
115
191
|
},
|
|
116
192
|
{
|
|
117
193
|
name: 'Import',
|
|
118
194
|
value: 'import',
|
|
119
195
|
description: 'Import graph data from JSON',
|
|
196
|
+
action: 'Import graph data',
|
|
120
197
|
},
|
|
198
|
+
],
|
|
199
|
+
default: 'export',
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
displayName: 'Operation',
|
|
203
|
+
name: 'operation',
|
|
204
|
+
type: 'options',
|
|
205
|
+
noDataExpression: true,
|
|
206
|
+
displayOptions: { show: { resource: ['analysis'] } },
|
|
207
|
+
options: [
|
|
121
208
|
{
|
|
122
209
|
name: 'Detect Communities',
|
|
123
210
|
value: 'detectCommunities',
|
|
124
211
|
description: 'Detect entity communities/clusters using label propagation',
|
|
212
|
+
action: 'Detect communities',
|
|
125
213
|
},
|
|
126
214
|
],
|
|
127
|
-
default: '
|
|
215
|
+
default: 'detectCommunities',
|
|
128
216
|
},
|
|
129
217
|
{
|
|
130
218
|
displayName: 'Group ID',
|
|
@@ -354,6 +442,7 @@ class EngramAdmin {
|
|
|
354
442
|
var _a;
|
|
355
443
|
const items = this.getInputData();
|
|
356
444
|
const backend = this.getNodeParameter('backend', 0, 'embedded');
|
|
445
|
+
const resource = this.getNodeParameter('resource', 0, 'monitoring');
|
|
357
446
|
const operation = this.getNodeParameter('operation', 0);
|
|
358
447
|
let storage;
|
|
359
448
|
if (backend === 'neo4j') {
|
|
@@ -375,414 +464,43 @@ class EngramAdmin {
|
|
|
375
464
|
}
|
|
376
465
|
await storage.initialize();
|
|
377
466
|
const returnData = [];
|
|
467
|
+
const ctx = this;
|
|
378
468
|
try {
|
|
379
469
|
for (let i = 0; i < items.length; i++) {
|
|
380
470
|
try {
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
data_span_days: baseStats.oldest_episode && baseStats.newest_episode
|
|
402
|
-
? Math.round(((new Date(baseStats.newest_episode).getTime() -
|
|
403
|
-
new Date(baseStats.oldest_episode).getTime()) /
|
|
404
|
-
(1000 * 60 * 60 * 24)) *
|
|
405
|
-
10) / 10
|
|
406
|
-
: null,
|
|
407
|
-
};
|
|
408
|
-
returnData.push({ json: enhancedStats });
|
|
409
|
-
break;
|
|
410
|
-
}
|
|
411
|
-
case 'listGroups': {
|
|
412
|
-
const includeStats = this.getNodeParameter('includeStats', i, true);
|
|
413
|
-
const limit = this.getNodeParameter('limit', i, 100);
|
|
414
|
-
const globalStats = await storage.getStats();
|
|
415
|
-
const allGroupIds = globalStats.group_ids.slice(0, limit);
|
|
416
|
-
if (includeStats) {
|
|
417
|
-
for (const gid of allGroupIds) {
|
|
418
|
-
const gStats = await storage.getStats(gid);
|
|
419
|
-
returnData.push({
|
|
420
|
-
json: {
|
|
421
|
-
group_id: gid,
|
|
422
|
-
entity_count: gStats.entity_count,
|
|
423
|
-
edge_count: gStats.edge_count,
|
|
424
|
-
episode_count: gStats.episode_count,
|
|
425
|
-
entity_types: gStats.entity_types,
|
|
426
|
-
oldest_episode: gStats.oldest_episode,
|
|
427
|
-
newest_episode: gStats.newest_episode,
|
|
428
|
-
},
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
else {
|
|
433
|
-
for (const gid of allGroupIds) {
|
|
434
|
-
returnData.push({ json: { group_id: gid } });
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
if (returnData.length === 0) {
|
|
438
|
-
returnData.push({
|
|
439
|
-
json: {
|
|
440
|
-
message: 'No groups found',
|
|
441
|
-
total_groups: 0,
|
|
442
|
-
},
|
|
443
|
-
});
|
|
444
|
-
}
|
|
445
|
-
break;
|
|
446
|
-
}
|
|
447
|
-
case 'groupStats': {
|
|
448
|
-
const groupId = this.getNodeParameter('groupId', i);
|
|
449
|
-
if (!groupId) {
|
|
450
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Group Stats operation', { itemIndex: i });
|
|
451
|
-
}
|
|
452
|
-
const gStats = await storage.getStats(groupId);
|
|
453
|
-
const gData = await storage.exportGraph(groupId);
|
|
454
|
-
const activeEdges = gData.edges.filter((e) => e.expired_at === null).length;
|
|
455
|
-
const expiredEdges = gData.edges.filter((e) => e.expired_at !== null).length;
|
|
456
|
-
const relationshipTypes = {};
|
|
457
|
-
for (const edge of gData.edges) {
|
|
458
|
-
relationshipTypes[edge.name] = (relationshipTypes[edge.name] || 0) + 1;
|
|
459
|
-
}
|
|
460
|
-
returnData.push({
|
|
461
|
-
json: {
|
|
462
|
-
group_id: groupId,
|
|
463
|
-
entity_count: gStats.entity_count,
|
|
464
|
-
edge_count: gStats.edge_count,
|
|
465
|
-
episode_count: gStats.episode_count,
|
|
466
|
-
entity_types: gStats.entity_types,
|
|
467
|
-
relationship_types: relationshipTypes,
|
|
468
|
-
active_edge_count: activeEdges,
|
|
469
|
-
expired_edge_count: expiredEdges,
|
|
470
|
-
avg_edges_per_entity: gStats.entity_count > 0
|
|
471
|
-
? Math.round((gStats.edge_count / gStats.entity_count) * 100) / 100
|
|
472
|
-
: 0,
|
|
473
|
-
oldest_episode: gStats.oldest_episode,
|
|
474
|
-
newest_episode: gStats.newest_episode,
|
|
475
|
-
data_span_days: gStats.oldest_episode && gStats.newest_episode
|
|
476
|
-
? Math.round(((new Date(gStats.newest_episode).getTime() -
|
|
477
|
-
new Date(gStats.oldest_episode).getTime()) /
|
|
478
|
-
(1000 * 60 * 60 * 24)) *
|
|
479
|
-
10) / 10
|
|
480
|
-
: null,
|
|
481
|
-
},
|
|
482
|
-
});
|
|
483
|
-
break;
|
|
484
|
-
}
|
|
485
|
-
case 'applyRetention': {
|
|
486
|
-
const groupId = this.getNodeParameter('groupId', i);
|
|
487
|
-
if (!groupId) {
|
|
488
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Apply Retention operation', { itemIndex: i });
|
|
489
|
-
}
|
|
490
|
-
const retentionType = this.getNodeParameter('retentionType', i);
|
|
491
|
-
const retentionValue = this.getNodeParameter('retentionValue', i);
|
|
492
|
-
const removed = await storage.applyRetention(groupId, {
|
|
493
|
-
type: retentionType,
|
|
494
|
-
value: retentionValue,
|
|
495
|
-
});
|
|
496
|
-
returnData.push({
|
|
497
|
-
json: {
|
|
498
|
-
success: true,
|
|
499
|
-
operation: 'applyRetention',
|
|
500
|
-
group_id: groupId,
|
|
501
|
-
policy: { type: retentionType, value: retentionValue },
|
|
502
|
-
episodes_removed: removed,
|
|
503
|
-
},
|
|
504
|
-
});
|
|
505
|
-
break;
|
|
506
|
-
}
|
|
507
|
-
case 'clearGroup': {
|
|
508
|
-
const groupId = this.getNodeParameter('groupId', i);
|
|
509
|
-
if (!groupId) {
|
|
510
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Clear Group operation', {
|
|
511
|
-
itemIndex: i,
|
|
512
|
-
description: 'Provide the session/group ID whose data you want to delete.',
|
|
513
|
-
});
|
|
514
|
-
}
|
|
515
|
-
await storage.clearGroup(groupId);
|
|
516
|
-
returnData.push({
|
|
517
|
-
json: { success: true, operation: 'clearGroup', group_id: groupId },
|
|
518
|
-
});
|
|
519
|
-
break;
|
|
520
|
-
}
|
|
521
|
-
case 'bulkClearGroups': {
|
|
522
|
-
const confirmDestructive = this.getNodeParameter('confirmDestructive', i, false);
|
|
523
|
-
if (!confirmDestructive) {
|
|
524
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Confirm Destructive must be enabled to proceed with Bulk Clear Groups', {
|
|
525
|
-
itemIndex: i,
|
|
526
|
-
description: 'This is a safety measure. Enable "Confirm Destructive" to proceed.',
|
|
527
|
-
});
|
|
528
|
-
}
|
|
529
|
-
const groupIdsRaw = this.getNodeParameter('groupIds', i);
|
|
530
|
-
let groupIds;
|
|
531
|
-
if (Array.isArray(groupIdsRaw)) {
|
|
532
|
-
groupIds = groupIdsRaw.filter((id) => typeof id === 'string' && id.length > 0);
|
|
533
|
-
}
|
|
534
|
-
else {
|
|
535
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group IDs must be a JSON array of strings', { itemIndex: i });
|
|
536
|
-
}
|
|
537
|
-
if (groupIds.length === 0) {
|
|
538
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group IDs array is empty', {
|
|
539
|
-
itemIndex: i,
|
|
540
|
-
});
|
|
541
|
-
}
|
|
542
|
-
const cleared = [];
|
|
543
|
-
const failed = [];
|
|
544
|
-
for (const gid of groupIds) {
|
|
545
|
-
try {
|
|
546
|
-
await storage.clearGroup(gid);
|
|
547
|
-
cleared.push(gid);
|
|
548
|
-
}
|
|
549
|
-
catch (err) {
|
|
550
|
-
failed.push({ group_id: gid, error: err.message });
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
returnData.push({
|
|
554
|
-
json: {
|
|
555
|
-
success: failed.length === 0,
|
|
556
|
-
operation: 'bulkClearGroups',
|
|
557
|
-
cleared,
|
|
558
|
-
failed,
|
|
559
|
-
total_cleared: cleared.length,
|
|
560
|
-
total_failed: failed.length,
|
|
561
|
-
},
|
|
562
|
-
});
|
|
563
|
-
break;
|
|
564
|
-
}
|
|
565
|
-
case 'clearAll': {
|
|
566
|
-
await storage.clearAll();
|
|
567
|
-
returnData.push({
|
|
568
|
-
json: { success: true, operation: 'clearAll' },
|
|
569
|
-
});
|
|
570
|
-
break;
|
|
571
|
-
}
|
|
572
|
-
case 'orphanedEntities': {
|
|
573
|
-
const groupId = this.getNodeParameter('groupId', i);
|
|
574
|
-
if (!groupId) {
|
|
575
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Orphaned Entities operation', { itemIndex: i });
|
|
576
|
-
}
|
|
577
|
-
const deleteOrphans = this.getNodeParameter('deleteOrphans', i, false);
|
|
578
|
-
const entities = await storage.listEntities(groupId, { limit: 10000 });
|
|
579
|
-
const orphaned = [];
|
|
580
|
-
for (const entity of entities) {
|
|
581
|
-
const edges = await storage.getEdgesForEntity(entity.uuid);
|
|
582
|
-
if (edges.length === 0) {
|
|
583
|
-
orphaned.push({
|
|
584
|
-
uuid: entity.uuid,
|
|
585
|
-
name: entity.name,
|
|
586
|
-
entity_type: entity.entity_type,
|
|
587
|
-
summary: entity.summary,
|
|
588
|
-
created_at: entity.created_at,
|
|
589
|
-
});
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
if (deleteOrphans) {
|
|
593
|
-
for (const o of orphaned) {
|
|
594
|
-
await storage.deleteEntity(o.uuid);
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
returnData.push({
|
|
598
|
-
json: {
|
|
599
|
-
operation: 'orphanedEntities',
|
|
600
|
-
group_id: groupId,
|
|
601
|
-
orphaned,
|
|
602
|
-
total_orphaned: orphaned.length,
|
|
603
|
-
deleted: deleteOrphans,
|
|
604
|
-
},
|
|
605
|
-
});
|
|
606
|
-
break;
|
|
607
|
-
}
|
|
608
|
-
case 'duplicateEntities': {
|
|
609
|
-
const groupId = this.getNodeParameter('groupId', i);
|
|
610
|
-
if (!groupId) {
|
|
611
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Duplicate Entities operation', { itemIndex: i });
|
|
612
|
-
}
|
|
613
|
-
const entities = await storage.listEntities(groupId, { limit: 10000 });
|
|
614
|
-
const nameMap = new Map();
|
|
615
|
-
for (const entity of entities) {
|
|
616
|
-
const key = entity.name.toLowerCase().trim();
|
|
617
|
-
if (!nameMap.has(key))
|
|
618
|
-
nameMap.set(key, []);
|
|
619
|
-
nameMap.get(key).push(entity);
|
|
620
|
-
}
|
|
621
|
-
const duplicateGroups = [];
|
|
622
|
-
for (const [canonicalName, group] of nameMap) {
|
|
623
|
-
if (group.length > 1) {
|
|
624
|
-
const enriched = [];
|
|
625
|
-
for (const e of group) {
|
|
626
|
-
const edges = await storage.getEdgesForEntity(e.uuid);
|
|
627
|
-
enriched.push({
|
|
628
|
-
uuid: e.uuid,
|
|
629
|
-
name: e.name,
|
|
630
|
-
entity_type: e.entity_type,
|
|
631
|
-
edge_count: edges.length,
|
|
632
|
-
created_at: e.created_at,
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
duplicateGroups.push({
|
|
636
|
-
canonical_name: canonicalName,
|
|
637
|
-
count: group.length,
|
|
638
|
-
entities: enriched,
|
|
639
|
-
});
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
returnData.push({
|
|
643
|
-
json: {
|
|
644
|
-
operation: 'duplicateEntities',
|
|
645
|
-
group_id: groupId,
|
|
646
|
-
duplicate_groups: duplicateGroups,
|
|
647
|
-
total_duplicate_groups: duplicateGroups.length,
|
|
648
|
-
},
|
|
649
|
-
});
|
|
650
|
-
break;
|
|
651
|
-
}
|
|
652
|
-
case 'expireStaleEdges': {
|
|
653
|
-
const groupId = this.getNodeParameter('groupId', i);
|
|
654
|
-
if (!groupId) {
|
|
655
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Expire Stale Edges operation', { itemIndex: i });
|
|
656
|
-
}
|
|
657
|
-
const dryRun = this.getNodeParameter('dryRun', i, true);
|
|
658
|
-
const data = await storage.exportGraph(groupId);
|
|
659
|
-
const entityUuids = new Set(data.entities.map((e) => e.uuid));
|
|
660
|
-
const staleEdges = [];
|
|
661
|
-
for (const edge of data.edges) {
|
|
662
|
-
if (edge.expired_at)
|
|
663
|
-
continue;
|
|
664
|
-
if (!entityUuids.has(edge.source_node_uuid) ||
|
|
665
|
-
!entityUuids.has(edge.target_node_uuid)) {
|
|
666
|
-
staleEdges.push({
|
|
667
|
-
uuid: edge.uuid,
|
|
668
|
-
name: edge.name,
|
|
669
|
-
fact: edge.fact,
|
|
670
|
-
reason: 'dangling_reference',
|
|
671
|
-
});
|
|
672
|
-
}
|
|
673
|
-
else if (edge.invalid_at && new Date(edge.invalid_at) < new Date()) {
|
|
674
|
-
staleEdges.push({
|
|
675
|
-
uuid: edge.uuid,
|
|
676
|
-
name: edge.name,
|
|
677
|
-
fact: edge.fact,
|
|
678
|
-
reason: 'past_invalid_at',
|
|
679
|
-
});
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
if (!dryRun) {
|
|
683
|
-
for (const edge of staleEdges) {
|
|
684
|
-
await storage.updateEdge(edge.uuid, { expired_at: (0, temporal_1.nowIso)() });
|
|
685
|
-
}
|
|
686
|
-
}
|
|
687
|
-
returnData.push({
|
|
688
|
-
json: {
|
|
689
|
-
operation: 'expireStaleEdges',
|
|
690
|
-
group_id: groupId,
|
|
691
|
-
dry_run: dryRun,
|
|
692
|
-
stale_edges: staleEdges,
|
|
693
|
-
total_stale: staleEdges.length,
|
|
694
|
-
expired: !dryRun,
|
|
695
|
-
},
|
|
696
|
-
});
|
|
697
|
-
break;
|
|
698
|
-
}
|
|
699
|
-
case 'detectCommunities': {
|
|
700
|
-
const groupId = this.getNodeParameter('groupId', i);
|
|
701
|
-
if (!groupId) {
|
|
702
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Group ID is required for Detect Communities operation', { itemIndex: i });
|
|
703
|
-
}
|
|
704
|
-
const minCommunitySize = this.getNodeParameter('minCommunitySize', i, 2);
|
|
705
|
-
const generateSummaries = this.getNodeParameter('generateSummaries', i, false);
|
|
706
|
-
const detector = new CommunityDetector_1.CommunityDetector(storage);
|
|
707
|
-
let result = await detector.detect(groupId, { minCommunitySize });
|
|
708
|
-
if (generateSummaries) {
|
|
709
|
-
const extractionCreds = await this.getCredentials('engramExtractionApi');
|
|
710
|
-
const model = this.getNodeParameter('summaryModel', i, '');
|
|
711
|
-
const concurrency = this.getNodeParameter('summaryConcurrency', i, 3);
|
|
712
|
-
if (!model) {
|
|
713
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Summary Model is required when Generate Summaries is enabled', { itemIndex: i });
|
|
714
|
-
}
|
|
715
|
-
const llm = new LlmClient_1.LlmClient({
|
|
716
|
-
apiKey: extractionCreds.apiKey,
|
|
717
|
-
baseUrl: extractionCreds.baseUrl,
|
|
718
|
-
model,
|
|
719
|
-
});
|
|
720
|
-
const summarizer = new CommunitySummarizer_1.CommunitySummarizer(llm);
|
|
721
|
-
result = await summarizer.summarizeAll(result, concurrency);
|
|
722
|
-
}
|
|
723
|
-
returnData.push({
|
|
724
|
-
json: {
|
|
725
|
-
total_entities: result.total_entities,
|
|
726
|
-
unclustered_entities: result.unclustered_entities,
|
|
727
|
-
detection_method: result.detection_method,
|
|
728
|
-
community_count: result.communities.length,
|
|
729
|
-
communities: result.communities.map((c) => ({
|
|
730
|
-
id: c.id,
|
|
731
|
-
label: c.label,
|
|
732
|
-
summary: c.summary,
|
|
733
|
-
entity_count: c.entity_count,
|
|
734
|
-
edge_count: c.edge_count,
|
|
735
|
-
key_entities: c.key_entities,
|
|
736
|
-
members: c.members.map((m) => ({
|
|
737
|
-
uuid: m.entity.uuid,
|
|
738
|
-
name: m.entity.name,
|
|
739
|
-
entity_type: m.entity.entity_type,
|
|
740
|
-
edge_count: m.edges.length,
|
|
741
|
-
})),
|
|
742
|
-
})),
|
|
743
|
-
},
|
|
744
|
-
});
|
|
745
|
-
break;
|
|
471
|
+
const beforeLength = returnData.length;
|
|
472
|
+
if (resource === 'monitoring') {
|
|
473
|
+
await executeMonitoring(ctx, storage, operation, i, returnData, backend);
|
|
474
|
+
}
|
|
475
|
+
else if (resource === 'lifecycle') {
|
|
476
|
+
await executeLifecycle(ctx, storage, operation, i, returnData);
|
|
477
|
+
}
|
|
478
|
+
else if (resource === 'hygiene') {
|
|
479
|
+
await executeHygiene(ctx, storage, operation, i, returnData);
|
|
480
|
+
}
|
|
481
|
+
else if (resource === 'portability') {
|
|
482
|
+
await executePortability(ctx, storage, operation, i, returnData);
|
|
483
|
+
}
|
|
484
|
+
else if (resource === 'analysis') {
|
|
485
|
+
await executeAnalysis(ctx, storage, operation, i, returnData);
|
|
486
|
+
}
|
|
487
|
+
if (returnData.length === beforeLength) {
|
|
488
|
+
await executeLifecycle(ctx, storage, operation, i, returnData);
|
|
489
|
+
if (returnData.length === beforeLength) {
|
|
490
|
+
await executeHygiene(ctx, storage, operation, i, returnData);
|
|
746
491
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
const data = await storage.exportGraph(groupId || undefined);
|
|
750
|
-
returnData.push({ json: data });
|
|
751
|
-
break;
|
|
492
|
+
if (returnData.length === beforeLength) {
|
|
493
|
+
await executePortability(ctx, storage, operation, i, returnData);
|
|
752
494
|
}
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
if (!importData ||
|
|
756
|
-
typeof importData !== 'object' ||
|
|
757
|
-
!Array.isArray(importData.entities) ||
|
|
758
|
-
!Array.isArray(importData.edges) ||
|
|
759
|
-
!Array.isArray(importData.episodes)) {
|
|
760
|
-
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Invalid import data format', {
|
|
761
|
-
itemIndex: i,
|
|
762
|
-
description: 'Import data must be a JSON object with "entities", "edges", and "episodes" arrays. Use data from a previous Export operation.',
|
|
763
|
-
});
|
|
764
|
-
}
|
|
765
|
-
await storage.importGraph(importData);
|
|
766
|
-
const graphData = importData;
|
|
767
|
-
returnData.push({
|
|
768
|
-
json: {
|
|
769
|
-
success: true,
|
|
770
|
-
operation: 'import',
|
|
771
|
-
imported: {
|
|
772
|
-
entities: graphData.entities.length,
|
|
773
|
-
edges: graphData.edges.length,
|
|
774
|
-
episodes: graphData.episodes.length,
|
|
775
|
-
},
|
|
776
|
-
},
|
|
777
|
-
});
|
|
778
|
-
break;
|
|
495
|
+
if (returnData.length === beforeLength) {
|
|
496
|
+
await executeAnalysis(ctx, storage, operation, i, returnData);
|
|
779
497
|
}
|
|
780
498
|
}
|
|
781
499
|
}
|
|
782
500
|
catch (error) {
|
|
783
501
|
if (error instanceof n8n_workflow_1.NodeOperationError)
|
|
784
502
|
throw error;
|
|
785
|
-
throw new n8n_workflow_1.NodeOperationError(
|
|
503
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), `Engram Admin error: ${error.message}`, { itemIndex: i });
|
|
786
504
|
}
|
|
787
505
|
}
|
|
788
506
|
}
|
|
@@ -795,4 +513,398 @@ class EngramAdmin {
|
|
|
795
513
|
}
|
|
796
514
|
}
|
|
797
515
|
exports.EngramAdmin = EngramAdmin;
|
|
516
|
+
async function executeMonitoring(ctx, storage, operation, i, returnData, backend) {
|
|
517
|
+
if (operation === 'stats') {
|
|
518
|
+
const groupId = ctx.getNodeParameter('groupIdFilter', i, '').trim();
|
|
519
|
+
const baseStats = await storage.getStats(groupId || undefined);
|
|
520
|
+
const data = await storage.exportGraph(groupId || undefined);
|
|
521
|
+
const activeEdges = data.edges.filter((e) => e.expired_at === null).length;
|
|
522
|
+
const expiredEdges = data.edges.filter((e) => e.expired_at !== null).length;
|
|
523
|
+
const groupCount = baseStats.group_ids.length;
|
|
524
|
+
const enhancedStats = {
|
|
525
|
+
...baseStats,
|
|
526
|
+
group_count: groupCount,
|
|
527
|
+
active_edge_count: activeEdges,
|
|
528
|
+
expired_edge_count: expiredEdges,
|
|
529
|
+
avg_edges_per_entity: baseStats.entity_count > 0
|
|
530
|
+
? Math.round((baseStats.edge_count / baseStats.entity_count) * 100) / 100
|
|
531
|
+
: 0,
|
|
532
|
+
avg_episodes_per_group: groupCount > 0 ? Math.round((baseStats.episode_count / groupCount) * 100) / 100 : 0,
|
|
533
|
+
storage_backend: backend,
|
|
534
|
+
data_span_days: baseStats.oldest_episode && baseStats.newest_episode
|
|
535
|
+
? Math.round(((new Date(baseStats.newest_episode).getTime() -
|
|
536
|
+
new Date(baseStats.oldest_episode).getTime()) /
|
|
537
|
+
(1000 * 60 * 60 * 24)) *
|
|
538
|
+
10) / 10
|
|
539
|
+
: null,
|
|
540
|
+
};
|
|
541
|
+
returnData.push({ json: enhancedStats });
|
|
542
|
+
}
|
|
543
|
+
else if (operation === 'listGroups') {
|
|
544
|
+
const includeStats = ctx.getNodeParameter('includeStats', i, true);
|
|
545
|
+
const limit = ctx.getNodeParameter('limit', i, 100);
|
|
546
|
+
const globalStats = await storage.getStats();
|
|
547
|
+
const allGroupIds = globalStats.group_ids.slice(0, limit);
|
|
548
|
+
if (includeStats) {
|
|
549
|
+
for (const gid of allGroupIds) {
|
|
550
|
+
const gStats = await storage.getStats(gid);
|
|
551
|
+
returnData.push({
|
|
552
|
+
json: {
|
|
553
|
+
group_id: gid,
|
|
554
|
+
entity_count: gStats.entity_count,
|
|
555
|
+
edge_count: gStats.edge_count,
|
|
556
|
+
episode_count: gStats.episode_count,
|
|
557
|
+
entity_types: gStats.entity_types,
|
|
558
|
+
oldest_episode: gStats.oldest_episode,
|
|
559
|
+
newest_episode: gStats.newest_episode,
|
|
560
|
+
},
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
else {
|
|
565
|
+
for (const gid of allGroupIds) {
|
|
566
|
+
returnData.push({ json: { group_id: gid } });
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
if (returnData.length === 0) {
|
|
570
|
+
returnData.push({
|
|
571
|
+
json: {
|
|
572
|
+
message: 'No groups found',
|
|
573
|
+
total_groups: 0,
|
|
574
|
+
},
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
else if (operation === 'groupStats') {
|
|
579
|
+
const groupId = ctx.getNodeParameter('groupId', i).trim();
|
|
580
|
+
if (!groupId) {
|
|
581
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Group ID is required for Group Stats operation', { itemIndex: i });
|
|
582
|
+
}
|
|
583
|
+
const gStats = await storage.getStats(groupId);
|
|
584
|
+
const gData = await storage.exportGraph(groupId);
|
|
585
|
+
const activeEdges = gData.edges.filter((e) => e.expired_at === null).length;
|
|
586
|
+
const expiredEdges = gData.edges.filter((e) => e.expired_at !== null).length;
|
|
587
|
+
const relationshipTypes = {};
|
|
588
|
+
for (const edge of gData.edges) {
|
|
589
|
+
relationshipTypes[edge.name] = (relationshipTypes[edge.name] || 0) + 1;
|
|
590
|
+
}
|
|
591
|
+
returnData.push({
|
|
592
|
+
json: {
|
|
593
|
+
group_id: groupId,
|
|
594
|
+
entity_count: gStats.entity_count,
|
|
595
|
+
edge_count: gStats.edge_count,
|
|
596
|
+
episode_count: gStats.episode_count,
|
|
597
|
+
entity_types: gStats.entity_types,
|
|
598
|
+
relationship_types: relationshipTypes,
|
|
599
|
+
active_edge_count: activeEdges,
|
|
600
|
+
expired_edge_count: expiredEdges,
|
|
601
|
+
avg_edges_per_entity: gStats.entity_count > 0
|
|
602
|
+
? Math.round((gStats.edge_count / gStats.entity_count) * 100) / 100
|
|
603
|
+
: 0,
|
|
604
|
+
oldest_episode: gStats.oldest_episode,
|
|
605
|
+
newest_episode: gStats.newest_episode,
|
|
606
|
+
data_span_days: gStats.oldest_episode && gStats.newest_episode
|
|
607
|
+
? Math.round(((new Date(gStats.newest_episode).getTime() -
|
|
608
|
+
new Date(gStats.oldest_episode).getTime()) /
|
|
609
|
+
(1000 * 60 * 60 * 24)) *
|
|
610
|
+
10) / 10
|
|
611
|
+
: null,
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
async function executeLifecycle(ctx, storage, operation, i, returnData) {
|
|
617
|
+
if (operation === 'applyRetention') {
|
|
618
|
+
const groupId = ctx.getNodeParameter('groupId', i).trim();
|
|
619
|
+
if (!groupId) {
|
|
620
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Group ID is required for Apply Retention operation', { itemIndex: i });
|
|
621
|
+
}
|
|
622
|
+
const retentionType = ctx.getNodeParameter('retentionType', i);
|
|
623
|
+
const retentionValue = ctx.getNodeParameter('retentionValue', i);
|
|
624
|
+
const removed = await storage.applyRetention(groupId, {
|
|
625
|
+
type: retentionType,
|
|
626
|
+
value: retentionValue,
|
|
627
|
+
});
|
|
628
|
+
returnData.push({
|
|
629
|
+
json: {
|
|
630
|
+
success: true,
|
|
631
|
+
operation: 'applyRetention',
|
|
632
|
+
group_id: groupId,
|
|
633
|
+
policy: { type: retentionType, value: retentionValue },
|
|
634
|
+
episodes_removed: removed,
|
|
635
|
+
},
|
|
636
|
+
});
|
|
637
|
+
}
|
|
638
|
+
else if (operation === 'clearGroup') {
|
|
639
|
+
const groupId = ctx.getNodeParameter('groupId', i).trim();
|
|
640
|
+
if (!groupId) {
|
|
641
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Group ID is required for Clear Group operation', {
|
|
642
|
+
itemIndex: i,
|
|
643
|
+
description: 'Provide the session/group ID whose data you want to delete.',
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
await storage.clearGroup(groupId);
|
|
647
|
+
returnData.push({
|
|
648
|
+
json: { success: true, operation: 'clearGroup', group_id: groupId },
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
else if (operation === 'bulkClearGroups') {
|
|
652
|
+
const confirmDestructive = ctx.getNodeParameter('confirmDestructive', i, false);
|
|
653
|
+
if (!confirmDestructive) {
|
|
654
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Confirm Destructive must be enabled to proceed with Bulk Clear Groups', {
|
|
655
|
+
itemIndex: i,
|
|
656
|
+
description: 'This is a safety measure. Enable "Confirm Destructive" to proceed.',
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
const groupIdsRaw = ctx.getNodeParameter('groupIds', i);
|
|
660
|
+
let groupIds;
|
|
661
|
+
if (Array.isArray(groupIdsRaw)) {
|
|
662
|
+
groupIds = groupIdsRaw.filter((id) => typeof id === 'string' && id.length > 0);
|
|
663
|
+
}
|
|
664
|
+
else {
|
|
665
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Group IDs must be a JSON array of strings', {
|
|
666
|
+
itemIndex: i,
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
if (groupIds.length === 0) {
|
|
670
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Group IDs array is empty', {
|
|
671
|
+
itemIndex: i,
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
const cleared = [];
|
|
675
|
+
const failed = [];
|
|
676
|
+
for (const gid of groupIds) {
|
|
677
|
+
try {
|
|
678
|
+
await storage.clearGroup(gid);
|
|
679
|
+
cleared.push(gid);
|
|
680
|
+
}
|
|
681
|
+
catch (err) {
|
|
682
|
+
failed.push({ group_id: gid, error: err.message });
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
returnData.push({
|
|
686
|
+
json: {
|
|
687
|
+
success: failed.length === 0,
|
|
688
|
+
operation: 'bulkClearGroups',
|
|
689
|
+
cleared,
|
|
690
|
+
failed,
|
|
691
|
+
total_cleared: cleared.length,
|
|
692
|
+
total_failed: failed.length,
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
}
|
|
696
|
+
else if (operation === 'clearAll') {
|
|
697
|
+
await storage.clearAll();
|
|
698
|
+
returnData.push({
|
|
699
|
+
json: { success: true, operation: 'clearAll' },
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
async function executeHygiene(ctx, storage, operation, i, returnData) {
|
|
704
|
+
if (operation === 'orphanedEntities') {
|
|
705
|
+
const groupId = ctx.getNodeParameter('groupId', i).trim();
|
|
706
|
+
if (!groupId) {
|
|
707
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Group ID is required for Orphaned Entities operation', { itemIndex: i });
|
|
708
|
+
}
|
|
709
|
+
const deleteOrphans = ctx.getNodeParameter('deleteOrphans', i, false);
|
|
710
|
+
const entities = await storage.listEntities(groupId, { limit: 10000 });
|
|
711
|
+
const orphaned = [];
|
|
712
|
+
for (const entity of entities) {
|
|
713
|
+
const edges = await storage.getEdgesForEntity(entity.uuid);
|
|
714
|
+
if (edges.length === 0) {
|
|
715
|
+
orphaned.push({
|
|
716
|
+
uuid: entity.uuid,
|
|
717
|
+
name: entity.name,
|
|
718
|
+
entity_type: entity.entity_type,
|
|
719
|
+
summary: entity.summary,
|
|
720
|
+
created_at: entity.created_at,
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
if (deleteOrphans) {
|
|
725
|
+
for (const o of orphaned) {
|
|
726
|
+
await storage.deleteEntity(o.uuid);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
returnData.push({
|
|
730
|
+
json: {
|
|
731
|
+
operation: 'orphanedEntities',
|
|
732
|
+
group_id: groupId,
|
|
733
|
+
orphaned,
|
|
734
|
+
total_orphaned: orphaned.length,
|
|
735
|
+
deleted: deleteOrphans,
|
|
736
|
+
},
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
else if (operation === 'duplicateEntities') {
|
|
740
|
+
const groupId = ctx.getNodeParameter('groupId', i).trim();
|
|
741
|
+
if (!groupId) {
|
|
742
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Group ID is required for Duplicate Entities operation', { itemIndex: i });
|
|
743
|
+
}
|
|
744
|
+
const entities = await storage.listEntities(groupId, { limit: 10000 });
|
|
745
|
+
const nameMap = new Map();
|
|
746
|
+
for (const entity of entities) {
|
|
747
|
+
const key = entity.name.toLowerCase().trim();
|
|
748
|
+
if (!nameMap.has(key))
|
|
749
|
+
nameMap.set(key, []);
|
|
750
|
+
nameMap.get(key).push(entity);
|
|
751
|
+
}
|
|
752
|
+
const duplicateGroups = [];
|
|
753
|
+
for (const [canonicalName, group] of nameMap) {
|
|
754
|
+
if (group.length > 1) {
|
|
755
|
+
const enriched = [];
|
|
756
|
+
for (const e of group) {
|
|
757
|
+
const edges = await storage.getEdgesForEntity(e.uuid);
|
|
758
|
+
enriched.push({
|
|
759
|
+
uuid: e.uuid,
|
|
760
|
+
name: e.name,
|
|
761
|
+
entity_type: e.entity_type,
|
|
762
|
+
edge_count: edges.length,
|
|
763
|
+
created_at: e.created_at,
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
duplicateGroups.push({
|
|
767
|
+
canonical_name: canonicalName,
|
|
768
|
+
count: group.length,
|
|
769
|
+
entities: enriched,
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
returnData.push({
|
|
774
|
+
json: {
|
|
775
|
+
operation: 'duplicateEntities',
|
|
776
|
+
group_id: groupId,
|
|
777
|
+
duplicate_groups: duplicateGroups,
|
|
778
|
+
total_duplicate_groups: duplicateGroups.length,
|
|
779
|
+
},
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
else if (operation === 'expireStaleEdges') {
|
|
783
|
+
const groupId = ctx.getNodeParameter('groupId', i).trim();
|
|
784
|
+
if (!groupId) {
|
|
785
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Group ID is required for Expire Stale Edges operation', { itemIndex: i });
|
|
786
|
+
}
|
|
787
|
+
const dryRun = ctx.getNodeParameter('dryRun', i, true);
|
|
788
|
+
const data = await storage.exportGraph(groupId);
|
|
789
|
+
const entityUuids = new Set(data.entities.map((e) => e.uuid));
|
|
790
|
+
const staleEdges = [];
|
|
791
|
+
for (const edge of data.edges) {
|
|
792
|
+
if (edge.expired_at)
|
|
793
|
+
continue;
|
|
794
|
+
if (!entityUuids.has(edge.source_node_uuid) || !entityUuids.has(edge.target_node_uuid)) {
|
|
795
|
+
staleEdges.push({
|
|
796
|
+
uuid: edge.uuid,
|
|
797
|
+
name: edge.name,
|
|
798
|
+
fact: edge.fact,
|
|
799
|
+
reason: 'dangling_reference',
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
else if (edge.invalid_at && new Date(edge.invalid_at) < new Date()) {
|
|
803
|
+
staleEdges.push({
|
|
804
|
+
uuid: edge.uuid,
|
|
805
|
+
name: edge.name,
|
|
806
|
+
fact: edge.fact,
|
|
807
|
+
reason: 'past_invalid_at',
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
if (!dryRun) {
|
|
812
|
+
for (const edge of staleEdges) {
|
|
813
|
+
await storage.updateEdge(edge.uuid, { expired_at: (0, temporal_1.nowIso)() });
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
returnData.push({
|
|
817
|
+
json: {
|
|
818
|
+
operation: 'expireStaleEdges',
|
|
819
|
+
group_id: groupId,
|
|
820
|
+
dry_run: dryRun,
|
|
821
|
+
stale_edges: staleEdges,
|
|
822
|
+
total_stale: staleEdges.length,
|
|
823
|
+
expired: !dryRun,
|
|
824
|
+
},
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
async function executePortability(ctx, storage, operation, i, returnData) {
|
|
829
|
+
if (operation === 'export') {
|
|
830
|
+
const groupId = ctx.getNodeParameter('groupIdFilter', i, '').trim();
|
|
831
|
+
const data = await storage.exportGraph(groupId || undefined);
|
|
832
|
+
returnData.push({ json: data });
|
|
833
|
+
}
|
|
834
|
+
else if (operation === 'import') {
|
|
835
|
+
const importData = ctx.getNodeParameter('importData', i);
|
|
836
|
+
if (!importData ||
|
|
837
|
+
typeof importData !== 'object' ||
|
|
838
|
+
!Array.isArray(importData.entities) ||
|
|
839
|
+
!Array.isArray(importData.edges) ||
|
|
840
|
+
!Array.isArray(importData.episodes)) {
|
|
841
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Invalid import data format', {
|
|
842
|
+
itemIndex: i,
|
|
843
|
+
description: 'Import data must be a JSON object with "entities", "edges", and "episodes" arrays. Use data from a previous Export operation.',
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
await storage.importGraph(importData);
|
|
847
|
+
const graphData = importData;
|
|
848
|
+
returnData.push({
|
|
849
|
+
json: {
|
|
850
|
+
success: true,
|
|
851
|
+
operation: 'import',
|
|
852
|
+
imported: {
|
|
853
|
+
entities: graphData.entities.length,
|
|
854
|
+
edges: graphData.edges.length,
|
|
855
|
+
episodes: graphData.episodes.length,
|
|
856
|
+
},
|
|
857
|
+
},
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
async function executeAnalysis(ctx, storage, operation, i, returnData) {
|
|
862
|
+
if (operation === 'detectCommunities') {
|
|
863
|
+
const groupId = ctx.getNodeParameter('groupId', i).trim();
|
|
864
|
+
if (!groupId) {
|
|
865
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Group ID is required for Detect Communities operation', { itemIndex: i });
|
|
866
|
+
}
|
|
867
|
+
const minCommunitySize = ctx.getNodeParameter('minCommunitySize', i, 2);
|
|
868
|
+
const generateSummaries = ctx.getNodeParameter('generateSummaries', i, false);
|
|
869
|
+
const detector = new CommunityDetector_1.CommunityDetector(storage);
|
|
870
|
+
let result = await detector.detect(groupId, { minCommunitySize });
|
|
871
|
+
if (generateSummaries) {
|
|
872
|
+
const extractionCreds = await ctx.getCredentials('engramExtractionApi');
|
|
873
|
+
const model = ctx.getNodeParameter('summaryModel', i, '');
|
|
874
|
+
const concurrency = ctx.getNodeParameter('summaryConcurrency', i, 3);
|
|
875
|
+
if (!model) {
|
|
876
|
+
throw new n8n_workflow_1.NodeOperationError(ctx.getNode(), 'Summary Model is required when Generate Summaries is enabled', { itemIndex: i });
|
|
877
|
+
}
|
|
878
|
+
const llm = new LlmClient_1.LlmClient({
|
|
879
|
+
apiKey: extractionCreds.apiKey,
|
|
880
|
+
baseUrl: extractionCreds.baseUrl,
|
|
881
|
+
model,
|
|
882
|
+
});
|
|
883
|
+
const summarizer = new CommunitySummarizer_1.CommunitySummarizer(llm);
|
|
884
|
+
result = await summarizer.summarizeAll(result, concurrency);
|
|
885
|
+
}
|
|
886
|
+
returnData.push({
|
|
887
|
+
json: {
|
|
888
|
+
total_entities: result.total_entities,
|
|
889
|
+
unclustered_entities: result.unclustered_entities,
|
|
890
|
+
detection_method: result.detection_method,
|
|
891
|
+
community_count: result.communities.length,
|
|
892
|
+
communities: result.communities.map((c) => ({
|
|
893
|
+
id: c.id,
|
|
894
|
+
label: c.label,
|
|
895
|
+
summary: c.summary,
|
|
896
|
+
entity_count: c.entity_count,
|
|
897
|
+
edge_count: c.edge_count,
|
|
898
|
+
key_entities: c.key_entities,
|
|
899
|
+
members: c.members.map((m) => ({
|
|
900
|
+
uuid: m.entity.uuid,
|
|
901
|
+
name: m.entity.name,
|
|
902
|
+
entity_type: m.entity.entity_type,
|
|
903
|
+
edge_count: m.edges.length,
|
|
904
|
+
})),
|
|
905
|
+
})),
|
|
906
|
+
},
|
|
907
|
+
});
|
|
908
|
+
}
|
|
909
|
+
}
|
|
798
910
|
//# sourceMappingURL=EngramAdmin.node.js.map
|