superlocalmemory 3.4.10 → 3.4.12
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 +17 -11
- package/docs/skill-evolution.md +77 -10
- package/ide/hooks/tool-event-hook.sh +4 -4
- package/package.json +1 -1
- package/pyproject.toml +3 -2
- package/src/superlocalmemory/cli/commands.py +170 -0
- package/src/superlocalmemory/cli/main.py +21 -0
- package/src/superlocalmemory/cli/setup_wizard.py +54 -11
- package/src/superlocalmemory/core/config.py +35 -0
- package/src/superlocalmemory/core/consolidation_engine.py +128 -0
- package/src/superlocalmemory/core/embedding_worker.py +1 -1
- package/src/superlocalmemory/core/engine.py +12 -0
- package/src/superlocalmemory/core/fact_consolidator.py +425 -0
- package/src/superlocalmemory/core/graph_pruner.py +290 -0
- package/src/superlocalmemory/core/maintenance_scheduler.py +20 -0
- package/src/superlocalmemory/core/recall_pipeline.py +9 -0
- package/src/superlocalmemory/core/tier_manager.py +325 -0
- package/src/superlocalmemory/encoding/entity_resolver.py +6 -5
- package/src/superlocalmemory/evolution/__init__.py +29 -0
- package/src/superlocalmemory/evolution/blind_verifier.py +115 -0
- package/src/superlocalmemory/evolution/evolution_store.py +302 -0
- package/src/superlocalmemory/evolution/mutation_generator.py +181 -0
- package/src/superlocalmemory/evolution/skill_evolver.py +555 -0
- package/src/superlocalmemory/evolution/triggers.py +367 -0
- package/src/superlocalmemory/evolution/types.py +92 -0
- package/src/superlocalmemory/hooks/hook_handlers.py +13 -0
- package/src/superlocalmemory/learning/skill_performance_miner.py +44 -11
- package/src/superlocalmemory/mcp/server.py +4 -0
- package/src/superlocalmemory/mcp/tools_evolution.py +338 -0
- package/src/superlocalmemory/retrieval/engine.py +98 -11
- package/src/superlocalmemory/retrieval/entity_channel.py +118 -0
- package/src/superlocalmemory/retrieval/forgetting_filter.py +22 -7
- package/src/superlocalmemory/retrieval/strategy.py +2 -2
- package/src/superlocalmemory/server/routes/behavioral.py +19 -15
- package/src/superlocalmemory/server/routes/evolution.py +213 -0
- package/src/superlocalmemory/server/routes/tiers.py +195 -0
- package/src/superlocalmemory/server/unified_daemon.py +39 -5
- package/src/superlocalmemory/storage/schema_v3411.py +149 -0
- package/src/superlocalmemory/ui/index.html +5 -2
- package/src/superlocalmemory/ui/js/lifecycle.js +83 -0
- package/src/superlocalmemory/ui/js/ng-skills.js +394 -10
- package/src/superlocalmemory.egg-info/PKG-INFO +614 -0
- package/src/superlocalmemory.egg-info/SOURCES.txt +335 -0
- package/src/superlocalmemory.egg-info/dependency_links.txt +1 -0
- package/src/superlocalmemory.egg-info/entry_points.txt +2 -0
- package/src/superlocalmemory.egg-info/requires.txt +55 -0
- package/src/superlocalmemory.egg-info/top_level.txt +1 -0
|
@@ -8,8 +8,101 @@
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
10
|
window.loadSkillEvolution = function() {
|
|
11
|
+
fetchEvolutionEngine();
|
|
11
12
|
fetchSkillOverview();
|
|
12
13
|
fetchSkillPerformance();
|
|
14
|
+
fetchSkillLineage();
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function fetchEvolutionEngine() {
|
|
18
|
+
var container = document.getElementById('skills-overview-cards');
|
|
19
|
+
if (!container) return;
|
|
20
|
+
|
|
21
|
+
fetch('/api/evolution/status')
|
|
22
|
+
.then(function(r) { return r.json(); })
|
|
23
|
+
.then(function(data) {
|
|
24
|
+
var enabled = data.enabled || false;
|
|
25
|
+
var backend = data.backend || 'none';
|
|
26
|
+
var stats = data.stats || {};
|
|
27
|
+
|
|
28
|
+
var statusColor = enabled ? '#10b981' : '#888';
|
|
29
|
+
var statusText = enabled ? 'Enabled (' + backend + ')' : 'Disabled';
|
|
30
|
+
var statusIcon = enabled ? 'bi-lightning-charge-fill' : 'bi-lightning-charge';
|
|
31
|
+
|
|
32
|
+
var html = '<div class="card" style="padding:16px;margin-bottom:16px;border-left:3px solid ' + statusColor + '">' +
|
|
33
|
+
'<div style="display:flex;justify-content:space-between;align-items:center">' +
|
|
34
|
+
'<div>' +
|
|
35
|
+
'<div style="font-weight:600;font-size:1rem;margin-bottom:4px">' +
|
|
36
|
+
'<i class="bi ' + statusIcon + '" style="color:' + statusColor + ';margin-right:6px"></i>' +
|
|
37
|
+
'Evolution Engine: ' + statusText +
|
|
38
|
+
'</div>' +
|
|
39
|
+
'<div style="font-size:0.8125rem;color:var(--bs-body-color)">' +
|
|
40
|
+
(enabled
|
|
41
|
+
? 'Backend: <strong>' + escapeHtml(backend) + '</strong> | ' +
|
|
42
|
+
'Evolved: ' + (stats.promoted || 0) + ' | ' +
|
|
43
|
+
'Rejected: ' + (stats.rejected || 0) + ' | ' +
|
|
44
|
+
'Budget: ' + (stats.cycle_budget_remaining || 3) + ' remaining this cycle'
|
|
45
|
+
: 'Enable via CLI: <code>slm config set evolution.enabled true</code> or via <code>slm setup</code>') +
|
|
46
|
+
'</div>' +
|
|
47
|
+
'</div>' +
|
|
48
|
+
'<div style="display:flex;gap:8px">' +
|
|
49
|
+
(enabled
|
|
50
|
+
? '<button class="btn btn-sm btn-outline-success" onclick="triggerEvolution()"><i class="bi bi-play-fill"></i> Run Now</button>'
|
|
51
|
+
: '<button class="btn btn-sm btn-outline-primary" onclick="enableEvolution()"><i class="bi bi-power"></i> Enable</button>') +
|
|
52
|
+
'</div>' +
|
|
53
|
+
'</div>';
|
|
54
|
+
|
|
55
|
+
// Evolution history (if any)
|
|
56
|
+
if (data.recent && data.recent.length > 0) {
|
|
57
|
+
html += '<div style="margin-top:12px;border-top:1px solid var(--bs-border-color);padding-top:12px">' +
|
|
58
|
+
'<div style="font-size:0.8125rem;font-weight:600;margin-bottom:8px">Recent Evolution</div>';
|
|
59
|
+
data.recent.forEach(function(r) {
|
|
60
|
+
var sColor = r.status === 'promoted' ? '#10b981' : r.status === 'rejected' ? '#ef4444' : '#f59e0b';
|
|
61
|
+
html += '<div style="display:flex;justify-content:space-between;font-size:0.75rem;padding:4px 0;border-bottom:1px solid var(--bs-border-color)">' +
|
|
62
|
+
'<span><i class="bi bi-lightning-charge" style="color:#8b5cf6"></i> ' + escapeHtml(r.skill_name) + '</span>' +
|
|
63
|
+
'<span style="color:' + sColor + '">' + escapeHtml(r.status) + ' (' + escapeHtml(r.evolution_type) + ')</span>' +
|
|
64
|
+
'</div>';
|
|
65
|
+
});
|
|
66
|
+
html += '</div>';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
html += '</div>';
|
|
70
|
+
|
|
71
|
+
// Insert before the overview cards
|
|
72
|
+
var overviewInner = document.getElementById('skills-overview-inner');
|
|
73
|
+
if (overviewInner) {
|
|
74
|
+
overviewInner.insertAdjacentHTML('beforebegin', html);
|
|
75
|
+
} else {
|
|
76
|
+
container.insertAdjacentHTML('afterbegin', html);
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
.catch(function() {
|
|
80
|
+
// API not available yet — skip silently
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
window.enableEvolution = function() {
|
|
85
|
+
fetch('/api/evolution/enable', { method: 'POST' })
|
|
86
|
+
.then(function(r) { return r.json(); })
|
|
87
|
+
.then(function(data) {
|
|
88
|
+
if (data.ok) {
|
|
89
|
+
loadSkillEvolution();
|
|
90
|
+
} else {
|
|
91
|
+
alert('Could not enable: ' + (data.error || 'unknown'));
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
.catch(function(err) { alert('Error: ' + err.message); });
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
window.triggerEvolution = function() {
|
|
98
|
+
fetch('/api/evolution/run', { method: 'POST' })
|
|
99
|
+
.then(function(r) { return r.json(); })
|
|
100
|
+
.then(function(data) {
|
|
101
|
+
alert('Evolution cycle: ' + (data.evolved || 0) + ' evolved, ' +
|
|
102
|
+
(data.rejected || 0) + ' rejected, ' + (data.candidates || 0) + ' candidates');
|
|
103
|
+
loadSkillEvolution();
|
|
104
|
+
})
|
|
105
|
+
.catch(function(err) { alert('Error: ' + err.message); });
|
|
13
106
|
};
|
|
14
107
|
|
|
15
108
|
function fetchSkillOverview() {
|
|
@@ -19,7 +112,7 @@
|
|
|
19
112
|
// Compatibility notice + ECC credit + docs links
|
|
20
113
|
var noticeHtml =
|
|
21
114
|
'<div class="card" style="padding:12px 16px;margin-bottom:16px;border-left:3px solid #8b5cf6">' +
|
|
22
|
-
'<div style="font-size:0.8125rem;color
|
|
115
|
+
'<div style="font-size:0.8125rem;color:var(--bs-body-color)">' +
|
|
23
116
|
'<i class="bi bi-info-circle" style="color:#8b5cf6;margin-right:6px"></i>' +
|
|
24
117
|
'<strong>Skill Evolution</strong> currently tracks <strong>Claude Code</strong> skills. ' +
|
|
25
118
|
'The <code>/api/v3/tool-event</code> endpoint accepts events from any IDE client. ' +
|
|
@@ -27,7 +120,7 @@
|
|
|
27
120
|
'<a href="https://github.com/affaan-m/everything-claude-code" target="_blank" style="color:#8b5cf6">Everything Claude Code (ECC)</a> ' +
|
|
28
121
|
'via <code>slm ingest --source ecc</code>.' +
|
|
29
122
|
'</div>' +
|
|
30
|
-
'<div style="font-size:0.75rem;color
|
|
123
|
+
'<div style="font-size:0.75rem;color:var(--bs-secondary-color);margin-top:8px">' +
|
|
31
124
|
'<a href="https://superlocalmemory.com/skill-evolution" target="_blank" style="color:#8b5cf6;margin-right:12px"><i class="bi bi-globe"></i> Learn more</a>' +
|
|
32
125
|
'<a href="https://github.com/qualixar/superlocalmemory/blob/main/docs/skill-evolution.md" target="_blank" style="color:#8b5cf6"><i class="bi bi-book"></i> Documentation</a>' +
|
|
33
126
|
'</div>' +
|
|
@@ -84,9 +177,9 @@
|
|
|
84
177
|
html += '<h5 style="margin-bottom:16px"><i class="bi bi-lightning-charge" style="color:#8b5cf6"></i> Skill Performance</h5>';
|
|
85
178
|
|
|
86
179
|
if (perfAssertions.length === 0 && events.length === 0) {
|
|
87
|
-
html += '<div class="card" style="padding:24px;text-align:center;color
|
|
180
|
+
html += '<div class="card" style="padding:24px;text-align:center;color:var(--bs-secondary-color)">' +
|
|
88
181
|
'<i class="bi bi-lightning-charge" style="font-size:2.5rem;display:block;margin-bottom:12px;opacity:0.3"></i>' +
|
|
89
|
-
'<div style="font-size:1rem;margin-bottom:4px;color
|
|
182
|
+
'<div style="font-size:1rem;margin-bottom:4px;color:var(--bs-body-color)">No skill performance data yet</div>' +
|
|
90
183
|
'<div style="font-size:0.8125rem">' +
|
|
91
184
|
'Skill tracking starts automatically after the enriched hook captures data.<br>' +
|
|
92
185
|
'Use skills in your sessions — performance assertions will appear after consolidation.' +
|
|
@@ -101,7 +194,7 @@
|
|
|
101
194
|
} else {
|
|
102
195
|
// We have events but no assertions yet (need consolidation)
|
|
103
196
|
html += '<div class="card" style="padding:16px;margin-bottom:16px">' +
|
|
104
|
-
'<div style="font-size:0.875rem;color
|
|
197
|
+
'<div style="font-size:0.875rem;color:var(--bs-body-color)">' +
|
|
105
198
|
'<i class="bi bi-info-circle" style="color:#8b5cf6;margin-right:6px"></i>' +
|
|
106
199
|
events.length + ' skill events collected. Run consolidation to generate performance assertions.' +
|
|
107
200
|
'</div>' +
|
|
@@ -140,10 +233,10 @@
|
|
|
140
233
|
'<div style="font-size:0.875rem">' +
|
|
141
234
|
'<strong>' + escapeHtml(a.trigger_condition || '') + '</strong>' +
|
|
142
235
|
'</div>' +
|
|
143
|
-
'<div style="font-size:0.8125rem;color
|
|
236
|
+
'<div style="font-size:0.8125rem;color:var(--bs-body-color);margin-top:4px">' +
|
|
144
237
|
escapeHtml(a.action || '') +
|
|
145
238
|
'</div>' +
|
|
146
|
-
'<div style="font-size:0.75rem;color
|
|
239
|
+
'<div style="font-size:0.75rem;color:var(--bs-secondary-color);margin-top:4px">' +
|
|
147
240
|
'Confidence: ' + ((a.confidence || 0) * 100).toFixed(0) + '%' +
|
|
148
241
|
'</div>' +
|
|
149
242
|
'</div></div>';
|
|
@@ -177,10 +270,10 @@
|
|
|
177
270
|
confPct + '%' +
|
|
178
271
|
'</span>' +
|
|
179
272
|
'</div>' +
|
|
180
|
-
'<div style="font-size:0.8125rem;color
|
|
273
|
+
'<div style="font-size:0.8125rem;color:var(--bs-body-color);margin-bottom:8px">' +
|
|
181
274
|
escapeHtml(assertion.action || 'No performance data yet') +
|
|
182
275
|
'</div>' +
|
|
183
|
-
'<div style="display:flex;justify-content:space-between;align-items:center;font-size:0.75rem;color
|
|
276
|
+
'<div style="display:flex;justify-content:space-between;align-items:center;font-size:0.75rem;color:var(--bs-secondary-color)">' +
|
|
184
277
|
'<span>Evidence: ' + (assertion.evidence_count || 0) + ' invocations</span>' +
|
|
185
278
|
'<span>Reinforced: ' + (assertion.reinforcement_count || 0) + 'x</span>' +
|
|
186
279
|
'</div>' +
|
|
@@ -215,7 +308,7 @@
|
|
|
215
308
|
return '<div class="col-md-3 col-6"><div class="card" style="padding:12px;text-align:center">' +
|
|
216
309
|
'<i class="bi ' + icon + '" style="color:' + color + ';font-size:1.125rem;display:block;margin-bottom:4px"></i>' +
|
|
217
310
|
'<div style="font-size:1.25rem;font-weight:600">' + value + '</div>' +
|
|
218
|
-
'<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.06em;color
|
|
311
|
+
'<div style="font-size:0.75rem;text-transform:uppercase;letter-spacing:0.06em;color:var(--bs-secondary-color)">' + label + '</div>' +
|
|
219
312
|
'</div></div>';
|
|
220
313
|
}
|
|
221
314
|
|
|
@@ -224,4 +317,295 @@
|
|
|
224
317
|
d.textContent = s || '';
|
|
225
318
|
return d.innerHTML;
|
|
226
319
|
}
|
|
320
|
+
|
|
321
|
+
// ── Skill Lineage (Version DAG) ────────────────────────────
|
|
322
|
+
|
|
323
|
+
var LINEAGE_COLORS = {
|
|
324
|
+
promoted: '#22c55e',
|
|
325
|
+
rejected: '#ef4444',
|
|
326
|
+
pending: '#eab308',
|
|
327
|
+
original: '#6b7280'
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
function fetchSkillLineage() {
|
|
331
|
+
var container = document.getElementById('skill-lineage-container');
|
|
332
|
+
if (!container) return;
|
|
333
|
+
|
|
334
|
+
fetch('/api/evolution/lineage')
|
|
335
|
+
.then(function(r) { return r.json(); })
|
|
336
|
+
.then(function(data) {
|
|
337
|
+
var lineage = data.lineage || [];
|
|
338
|
+
if (lineage.length === 0) {
|
|
339
|
+
container.innerHTML =
|
|
340
|
+
'<div class="card" style="padding:24px;text-align:center;color:var(--bs-secondary-color)">' +
|
|
341
|
+
'<i class="bi bi-diagram-3" style="font-size:2.5rem;display:block;margin-bottom:12px;opacity:0.3"></i>' +
|
|
342
|
+
'<div style="font-size:1rem;margin-bottom:4px;color:var(--bs-body-color)">No skill lineage data yet</div>' +
|
|
343
|
+
'<div style="font-size:0.8125rem">' +
|
|
344
|
+
'Lineage appears after skills evolve. Run an evolution cycle to generate skill versions.' +
|
|
345
|
+
'</div>' +
|
|
346
|
+
'</div>';
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
var html = '<h5 style="margin-bottom:16px"><i class="bi bi-diagram-3" style="color:#8b5cf6"></i> Skill Lineage</h5>';
|
|
350
|
+
html += '<div class="card" style="padding:16px;margin-bottom:16px">';
|
|
351
|
+
html += '<div id="lineage-dag-wrapper" style="max-height:400px;overflow:auto;position:relative"></div>';
|
|
352
|
+
html += '</div>';
|
|
353
|
+
html += '<div id="lineage-table-wrapper"></div>';
|
|
354
|
+
container.innerHTML = html;
|
|
355
|
+
renderLineageDAG(lineage);
|
|
356
|
+
renderLineageTable(lineage);
|
|
357
|
+
})
|
|
358
|
+
.catch(function() {
|
|
359
|
+
container.innerHTML = '';
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function renderLineageDAG(lineage) {
|
|
364
|
+
var wrapper = document.getElementById('lineage-dag-wrapper');
|
|
365
|
+
if (!wrapper) return;
|
|
366
|
+
|
|
367
|
+
// Build adjacency: id -> node, parent_skill_id -> children
|
|
368
|
+
var nodeMap = {};
|
|
369
|
+
var childrenMap = {};
|
|
370
|
+
var roots = [];
|
|
371
|
+
|
|
372
|
+
lineage.forEach(function(item) {
|
|
373
|
+
nodeMap[item.id] = item;
|
|
374
|
+
if (!childrenMap[item.id]) childrenMap[item.id] = [];
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
lineage.forEach(function(item) {
|
|
378
|
+
var pid = item.parent_skill_id;
|
|
379
|
+
if (pid && nodeMap[pid]) {
|
|
380
|
+
if (!childrenMap[pid]) childrenMap[pid] = [];
|
|
381
|
+
childrenMap[pid].push(item.id);
|
|
382
|
+
} else {
|
|
383
|
+
roots.push(item.id);
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// If no explicit roots found, treat all nodes as roots
|
|
388
|
+
if (roots.length === 0) {
|
|
389
|
+
lineage.forEach(function(item) { roots.push(item.id); });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Assign layers via BFS (Sugiyama layer assignment)
|
|
393
|
+
var layers = {};
|
|
394
|
+
var queue = [];
|
|
395
|
+
var visited = {};
|
|
396
|
+
roots.forEach(function(rid) {
|
|
397
|
+
layers[rid] = 0;
|
|
398
|
+
queue.push(rid);
|
|
399
|
+
visited[rid] = true;
|
|
400
|
+
});
|
|
401
|
+
var maxLayer = 0;
|
|
402
|
+
while (queue.length > 0) {
|
|
403
|
+
var nid = queue.shift();
|
|
404
|
+
var children = childrenMap[nid] || [];
|
|
405
|
+
children.forEach(function(cid) {
|
|
406
|
+
if (!visited[cid]) {
|
|
407
|
+
visited[cid] = true;
|
|
408
|
+
layers[cid] = (layers[nid] || 0) + 1;
|
|
409
|
+
if (layers[cid] > maxLayer) maxLayer = layers[cid];
|
|
410
|
+
queue.push(cid);
|
|
411
|
+
}
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Handle nodes not reached by BFS (disconnected)
|
|
416
|
+
lineage.forEach(function(item) {
|
|
417
|
+
if (layers[item.id] === undefined) {
|
|
418
|
+
layers[item.id] = 0;
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// Group nodes by layer
|
|
423
|
+
var layerGroups = {};
|
|
424
|
+
Object.keys(layers).forEach(function(nid) {
|
|
425
|
+
var l = layers[nid];
|
|
426
|
+
if (!layerGroups[l]) layerGroups[l] = [];
|
|
427
|
+
layerGroups[l].push(nid);
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
// Layout constants
|
|
431
|
+
var nodeW = 140;
|
|
432
|
+
var nodeH = 44;
|
|
433
|
+
var layerGap = 80;
|
|
434
|
+
var nodeGap = 24;
|
|
435
|
+
var padX = 20;
|
|
436
|
+
var padY = 20;
|
|
437
|
+
|
|
438
|
+
// Compute max width needed
|
|
439
|
+
var maxNodesInLayer = 0;
|
|
440
|
+
for (var l = 0; l <= maxLayer; l++) {
|
|
441
|
+
var count = (layerGroups[l] || []).length;
|
|
442
|
+
if (count > maxNodesInLayer) maxNodesInLayer = count;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
var svgW = Math.max(300, padX * 2 + maxNodesInLayer * (nodeW + nodeGap) - nodeGap);
|
|
446
|
+
var svgH = padY * 2 + (maxLayer + 1) * (nodeH + layerGap) - layerGap;
|
|
447
|
+
|
|
448
|
+
// Compute positions
|
|
449
|
+
var positions = {};
|
|
450
|
+
for (var ly = 0; ly <= maxLayer; ly++) {
|
|
451
|
+
var group = layerGroups[ly] || [];
|
|
452
|
+
var totalW = group.length * nodeW + (group.length - 1) * nodeGap;
|
|
453
|
+
var startX = (svgW - totalW) / 2;
|
|
454
|
+
var yPos = padY + ly * (nodeH + layerGap);
|
|
455
|
+
group.forEach(function(nid, idx) {
|
|
456
|
+
positions[nid] = {
|
|
457
|
+
x: startX + idx * (nodeW + nodeGap),
|
|
458
|
+
y: yPos
|
|
459
|
+
};
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Build SVG
|
|
464
|
+
var svg = '<svg xmlns="http://www.w3.org/2000/svg" width="' + svgW + '" height="' + svgH + '" ' +
|
|
465
|
+
'style="display:block;margin:0 auto;font-family:system-ui,-apple-system,sans-serif">';
|
|
466
|
+
|
|
467
|
+
// Arrowhead marker
|
|
468
|
+
svg += '<defs><marker id="lineage-arrow" viewBox="0 0 10 7" refX="10" refY="3.5" ' +
|
|
469
|
+
'markerWidth="8" markerHeight="6" orient="auto-start-reverse">' +
|
|
470
|
+
'<path d="M 0 0 L 10 3.5 L 0 7 z" fill="#888"/></marker></defs>';
|
|
471
|
+
|
|
472
|
+
// Draw edges
|
|
473
|
+
lineage.forEach(function(item) {
|
|
474
|
+
var pid = item.parent_skill_id;
|
|
475
|
+
if (pid && positions[pid] && positions[item.id]) {
|
|
476
|
+
var from = positions[pid];
|
|
477
|
+
var to = positions[item.id];
|
|
478
|
+
var x1 = from.x + nodeW / 2;
|
|
479
|
+
var y1 = from.y + nodeH;
|
|
480
|
+
var x2 = to.x + nodeW / 2;
|
|
481
|
+
var y2 = to.y;
|
|
482
|
+
// Curved path
|
|
483
|
+
var midY = (y1 + y2) / 2;
|
|
484
|
+
svg += '<path d="M ' + x1 + ' ' + y1 + ' C ' + x1 + ' ' + midY + ', ' + x2 + ' ' + midY + ', ' + x2 + ' ' + y2 + '" ' +
|
|
485
|
+
'fill="none" stroke="#888" stroke-width="1.5" marker-end="url(#lineage-arrow)"/>';
|
|
486
|
+
// Edge label
|
|
487
|
+
var etype = item.evolution_type || '';
|
|
488
|
+
if (etype) {
|
|
489
|
+
var lx = (x1 + x2) / 2;
|
|
490
|
+
var labelY = midY - 4;
|
|
491
|
+
svg += '<text x="' + lx + '" y="' + labelY + '" text-anchor="middle" ' +
|
|
492
|
+
'fill="#888" font-size="10" font-weight="500">' + escapeHtml(etype) + '</text>';
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
// Draw nodes
|
|
498
|
+
lineage.forEach(function(item) {
|
|
499
|
+
var pos = positions[item.id];
|
|
500
|
+
if (!pos) return;
|
|
501
|
+
var status = (item.status || 'original').toLowerCase();
|
|
502
|
+
var fillColor = LINEAGE_COLORS[status] || LINEAGE_COLORS.original;
|
|
503
|
+
var label = (item.skill_name || 'unknown');
|
|
504
|
+
if (label.length > 16) label = label.substring(0, 14) + '..';
|
|
505
|
+
|
|
506
|
+
svg += '<g class="lineage-node" data-id="' + item.id + '" style="cursor:pointer">';
|
|
507
|
+
svg += '<rect x="' + pos.x + '" y="' + pos.y + '" width="' + nodeW + '" height="' + nodeH + '" ' +
|
|
508
|
+
'rx="8" ry="8" fill="' + fillColor + '" fill-opacity="0.15" stroke="' + fillColor + '" stroke-width="2"/>';
|
|
509
|
+
// Node label (skill name)
|
|
510
|
+
svg += '<text x="' + (pos.x + nodeW / 2) + '" y="' + (pos.y + 18) + '" text-anchor="middle" ' +
|
|
511
|
+
'fill="' + fillColor + '" font-size="12" font-weight="600">' + escapeHtml(label) + '</text>';
|
|
512
|
+
// Status sub-label
|
|
513
|
+
svg += '<text x="' + (pos.x + nodeW / 2) + '" y="' + (pos.y + 34) + '" text-anchor="middle" ' +
|
|
514
|
+
'fill="' + fillColor + '" font-size="10" opacity="0.7">' + escapeHtml(status) + '</text>';
|
|
515
|
+
svg += '</g>';
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
svg += '</svg>';
|
|
519
|
+
wrapper.innerHTML = svg;
|
|
520
|
+
|
|
521
|
+
// Click handler: highlight row in table
|
|
522
|
+
wrapper.querySelectorAll('.lineage-node').forEach(function(g) {
|
|
523
|
+
g.addEventListener('click', function() {
|
|
524
|
+
var id = g.getAttribute('data-id');
|
|
525
|
+
highlightLineageRow(id);
|
|
526
|
+
});
|
|
527
|
+
});
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function renderLineageTable(lineage) {
|
|
531
|
+
var wrapper = document.getElementById('lineage-table-wrapper');
|
|
532
|
+
if (!wrapper) return;
|
|
533
|
+
|
|
534
|
+
var html = '<div class="card" style="padding:16px">' +
|
|
535
|
+
'<div style="font-weight:600;font-size:0.9375rem;margin-bottom:12px">' +
|
|
536
|
+
'<i class="bi bi-table" style="color:#8b5cf6;margin-right:6px"></i>Lineage Details' +
|
|
537
|
+
'</div>' +
|
|
538
|
+
'<div class="table-responsive"><table class="table table-sm table-hover" style="font-size:0.8125rem;margin-bottom:0">' +
|
|
539
|
+
'<thead><tr>' +
|
|
540
|
+
'<th>Skill Name</th>' +
|
|
541
|
+
'<th>Type</th>' +
|
|
542
|
+
'<th>Parent</th>' +
|
|
543
|
+
'<th>Status</th>' +
|
|
544
|
+
'<th>Verified</th>' +
|
|
545
|
+
'<th>Created</th>' +
|
|
546
|
+
'</tr></thead><tbody>';
|
|
547
|
+
|
|
548
|
+
// Build a quick lookup for parent names
|
|
549
|
+
var nameMap = {};
|
|
550
|
+
lineage.forEach(function(item) {
|
|
551
|
+
nameMap[item.id] = item.skill_name || item.id;
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
lineage.forEach(function(item) {
|
|
555
|
+
var status = (item.status || 'original').toLowerCase();
|
|
556
|
+
var color = LINEAGE_COLORS[status] || LINEAGE_COLORS.original;
|
|
557
|
+
var parentName = item.parent_skill_id ? (nameMap[item.parent_skill_id] || item.parent_skill_id) : '-';
|
|
558
|
+
var verified = item.blind_verified ? '<i class="bi bi-check-circle-fill" style="color:#22c55e"></i>' : '<i class="bi bi-dash-circle" style="color:var(--bs-secondary-color)"></i>';
|
|
559
|
+
var created = item.created_at ? new Date(item.created_at).toLocaleDateString() : '-';
|
|
560
|
+
|
|
561
|
+
html += '<tr class="lineage-table-row" data-id="' + item.id + '" style="cursor:pointer">' +
|
|
562
|
+
'<td style="font-weight:500">' + escapeHtml(item.skill_name || '') + '</td>' +
|
|
563
|
+
'<td><span class="badge" style="background:' + color + '20;color:' + color + ';font-size:0.6875rem">' +
|
|
564
|
+
escapeHtml(item.evolution_type || 'ORIGINAL') + '</span></td>' +
|
|
565
|
+
'<td>' + escapeHtml(parentName) + '</td>' +
|
|
566
|
+
'<td><span style="color:' + color + ';font-weight:500">' + escapeHtml(status) + '</span></td>' +
|
|
567
|
+
'<td>' + verified + '</td>' +
|
|
568
|
+
'<td style="color:var(--bs-secondary-color)">' + created + '</td>' +
|
|
569
|
+
'</tr>';
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
html += '</tbody></table></div></div>';
|
|
573
|
+
wrapper.innerHTML = html;
|
|
574
|
+
|
|
575
|
+
// Click rows to highlight in DAG
|
|
576
|
+
wrapper.querySelectorAll('.lineage-table-row').forEach(function(row) {
|
|
577
|
+
row.addEventListener('click', function() {
|
|
578
|
+
var id = row.getAttribute('data-id');
|
|
579
|
+
highlightLineageNode(id);
|
|
580
|
+
highlightLineageRow(id);
|
|
581
|
+
});
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function highlightLineageRow(id) {
|
|
586
|
+
// Clear previous highlights
|
|
587
|
+
document.querySelectorAll('.lineage-table-row').forEach(function(row) {
|
|
588
|
+
row.style.background = '';
|
|
589
|
+
});
|
|
590
|
+
var target = document.querySelector('.lineage-table-row[data-id="' + id + '"]');
|
|
591
|
+
if (target) {
|
|
592
|
+
target.style.background = 'rgba(139, 92, 246, 0.12)';
|
|
593
|
+
target.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function highlightLineageNode(id) {
|
|
598
|
+
// Reset all nodes
|
|
599
|
+
document.querySelectorAll('.lineage-node rect').forEach(function(rect) {
|
|
600
|
+
rect.setAttribute('stroke-width', '2');
|
|
601
|
+
});
|
|
602
|
+
// Highlight selected
|
|
603
|
+
var node = document.querySelector('.lineage-node[data-id="' + id + '"] rect');
|
|
604
|
+
if (node) {
|
|
605
|
+
node.setAttribute('stroke-width', '4');
|
|
606
|
+
node.closest('.lineage-node').parentElement.closest('svg')
|
|
607
|
+
.closest('#lineage-dag-wrapper')
|
|
608
|
+
.scrollTop = 0; // Scroll DAG to top if needed
|
|
609
|
+
}
|
|
610
|
+
}
|
|
227
611
|
})();
|