superlocalmemory 2.7.0 → 2.7.1
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/CHANGELOG.md +9 -0
- package/install.sh +26 -0
- package/package.json +1 -1
- package/ui/index.html +144 -0
- package/ui/js/init.js +4 -0
- package/ui/js/learning.js +318 -0
- package/ui_server.py +9 -0
package/CHANGELOG.md
CHANGED
|
@@ -16,6 +16,15 @@ SuperLocalMemory V2 - Intelligent local memory system for AI coding assistants.
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## [2.7.1] - 2026-02-16
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- **Learning Dashboard Tab** — New "Learning" tab in the web dashboard showing ranking phase, tech preferences, workflow patterns, source quality, engagement health, and privacy controls
|
|
23
|
+
- **Learning API Routes** — `/api/learning/status`, `/api/learning/reset`, `/api/learning/retrain` endpoints for the dashboard
|
|
24
|
+
- **One-click Reset** — Reset all learning data directly from the dashboard UI
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
19
28
|
## [2.7.0] - 2026-02-16
|
|
20
29
|
|
|
21
30
|
**Release Type:** Major Feature Release — "Your AI Learns You"
|
package/install.sh
CHANGED
|
@@ -187,6 +187,32 @@ if [ -f "${REPO_DIR}/api_server.py" ]; then
|
|
|
187
187
|
echo "✓ API server copied"
|
|
188
188
|
fi
|
|
189
189
|
|
|
190
|
+
# Copy UI server + dashboard files
|
|
191
|
+
if [ -f "${REPO_DIR}/ui_server.py" ]; then
|
|
192
|
+
cp "${REPO_DIR}/ui_server.py" "${INSTALL_DIR}/"
|
|
193
|
+
echo "✓ UI server copied"
|
|
194
|
+
fi
|
|
195
|
+
|
|
196
|
+
if [ -d "${REPO_DIR}/ui" ]; then
|
|
197
|
+
mkdir -p "${INSTALL_DIR}/ui/js"
|
|
198
|
+
cp "${REPO_DIR}/ui/index.html" "${INSTALL_DIR}/ui/" 2>/dev/null || true
|
|
199
|
+
cp "${REPO_DIR}/ui/js/"*.js "${INSTALL_DIR}/ui/js/" 2>/dev/null || true
|
|
200
|
+
echo "✓ Dashboard UI copied"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Copy route modules (v2.5+ dashboard API)
|
|
204
|
+
if [ -d "${REPO_DIR}/routes" ]; then
|
|
205
|
+
mkdir -p "${INSTALL_DIR}/routes"
|
|
206
|
+
cp "${REPO_DIR}/routes/"*.py "${INSTALL_DIR}/routes/"
|
|
207
|
+
echo "✓ Dashboard routes copied"
|
|
208
|
+
fi
|
|
209
|
+
|
|
210
|
+
# Copy MCP server
|
|
211
|
+
if [ -f "${REPO_DIR}/mcp_server.py" ]; then
|
|
212
|
+
cp "${REPO_DIR}/mcp_server.py" "${INSTALL_DIR}/"
|
|
213
|
+
echo "✓ MCP server copied"
|
|
214
|
+
fi
|
|
215
|
+
|
|
190
216
|
# Copy config if not exists
|
|
191
217
|
if [ ! -f "${INSTALL_DIR}/config.json" ]; then
|
|
192
218
|
echo "Creating default config..."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "superlocalmemory",
|
|
3
|
-
"version": "2.7.
|
|
3
|
+
"version": "2.7.1",
|
|
4
4
|
"description": "Your AI Finally Remembers You - Local-first intelligent memory system for AI assistants. Works with Claude, Cursor, Windsurf, VS Code/Copilot, Codex, and 17+ AI tools. 100% local, zero cloud dependencies.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-memory",
|
package/ui/index.html
CHANGED
|
@@ -720,6 +720,11 @@
|
|
|
720
720
|
<i class="bi bi-clock-history"></i> Timeline
|
|
721
721
|
</button>
|
|
722
722
|
</li>
|
|
723
|
+
<li class="nav-item">
|
|
724
|
+
<button class="nav-link" id="learning-tab" data-bs-toggle="tab" data-bs-target="#learning-pane">
|
|
725
|
+
<i class="bi bi-mortarboard"></i> Learning <span class="badge bg-warning text-dark" style="font-size:0.6rem;vertical-align:top;">v2.7</span>
|
|
726
|
+
</button>
|
|
727
|
+
</li>
|
|
723
728
|
<li class="nav-item">
|
|
724
729
|
<button class="nav-link" id="events-tab" data-bs-toggle="tab" data-bs-target="#events-pane">
|
|
725
730
|
<i class="bi bi-broadcast"></i> Live Events
|
|
@@ -906,6 +911,144 @@
|
|
|
906
911
|
</div>
|
|
907
912
|
</div>
|
|
908
913
|
|
|
914
|
+
<!-- Learning System (v2.7) -->
|
|
915
|
+
<div class="tab-pane fade" id="learning-pane">
|
|
916
|
+
<!-- Ranking Phase & Engagement -->
|
|
917
|
+
<div class="row g-3 mb-3">
|
|
918
|
+
<div class="col-md-4">
|
|
919
|
+
<div class="card p-3 text-center">
|
|
920
|
+
<div class="fw-bold text-muted small">RANKING PHASE</div>
|
|
921
|
+
<div class="fs-3 fw-bold" id="learning-phase" style="color: var(--bs-primary);">--</div>
|
|
922
|
+
<small class="text-muted" id="learning-phase-detail">Loading...</small>
|
|
923
|
+
</div>
|
|
924
|
+
</div>
|
|
925
|
+
<div class="col-md-4">
|
|
926
|
+
<div class="card p-3 text-center">
|
|
927
|
+
<div class="fw-bold text-muted small">FEEDBACK SIGNALS</div>
|
|
928
|
+
<div class="fs-3 fw-bold" id="learning-feedback-count">0</div>
|
|
929
|
+
<small class="text-muted" id="learning-feedback-detail">0 unique queries</small>
|
|
930
|
+
</div>
|
|
931
|
+
</div>
|
|
932
|
+
<div class="col-md-4">
|
|
933
|
+
<div class="card p-3 text-center">
|
|
934
|
+
<div class="fw-bold text-muted small">ENGAGEMENT HEALTH</div>
|
|
935
|
+
<div class="fs-3 fw-bold" id="learning-health" style="color: var(--bs-success);">--</div>
|
|
936
|
+
<small class="text-muted" id="learning-health-detail">Loading...</small>
|
|
937
|
+
</div>
|
|
938
|
+
</div>
|
|
939
|
+
</div>
|
|
940
|
+
|
|
941
|
+
<!-- Phase Progress Bar -->
|
|
942
|
+
<div class="card p-3 mb-3">
|
|
943
|
+
<h6 class="mb-2"><i class="bi bi-bar-chart-steps"></i> Adaptive Ranking Progress</h6>
|
|
944
|
+
<div class="d-flex align-items-center gap-2 mb-2">
|
|
945
|
+
<div class="flex-grow-1">
|
|
946
|
+
<div class="progress" style="height: 24px; border-radius: 12px;">
|
|
947
|
+
<div class="progress-bar" id="learning-progress" role="progressbar" style="width: 0%; border-radius: 12px; transition: width 0.8s ease;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">
|
|
948
|
+
</div>
|
|
949
|
+
</div>
|
|
950
|
+
</div>
|
|
951
|
+
</div>
|
|
952
|
+
<div class="d-flex justify-content-between small text-muted">
|
|
953
|
+
<span>Baseline (0)</span>
|
|
954
|
+
<span>Rule-Based (20+)</span>
|
|
955
|
+
<span>ML Model (200+)</span>
|
|
956
|
+
</div>
|
|
957
|
+
</div>
|
|
958
|
+
|
|
959
|
+
<div class="row g-3">
|
|
960
|
+
<!-- Tech Preferences (Layer 1) -->
|
|
961
|
+
<div class="col-md-6">
|
|
962
|
+
<div class="card p-3 h-100">
|
|
963
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
964
|
+
<h6 class="mb-0"><i class="bi bi-cpu"></i> Tech Preferences</h6>
|
|
965
|
+
<span class="badge bg-primary">Layer 1</span>
|
|
966
|
+
</div>
|
|
967
|
+
<div id="learning-tech-prefs">
|
|
968
|
+
<div class="text-center text-muted py-3">
|
|
969
|
+
<i class="bi bi-hourglass-split" style="font-size: 1.5rem;"></i>
|
|
970
|
+
<p class="mt-2 mb-0 small">Patterns detected after using recall with feedback</p>
|
|
971
|
+
</div>
|
|
972
|
+
</div>
|
|
973
|
+
</div>
|
|
974
|
+
</div>
|
|
975
|
+
|
|
976
|
+
<!-- Workflow Patterns (Layer 3) -->
|
|
977
|
+
<div class="col-md-6">
|
|
978
|
+
<div class="card p-3 h-100">
|
|
979
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
980
|
+
<h6 class="mb-0"><i class="bi bi-diagram-3"></i> Workflow Patterns</h6>
|
|
981
|
+
<span class="badge bg-info">Layer 3</span>
|
|
982
|
+
</div>
|
|
983
|
+
<div id="learning-workflows">
|
|
984
|
+
<div class="text-center text-muted py-3">
|
|
985
|
+
<i class="bi bi-hourglass-split" style="font-size: 1.5rem;"></i>
|
|
986
|
+
<p class="mt-2 mb-0 small">Sequences detected after 30+ memories</p>
|
|
987
|
+
</div>
|
|
988
|
+
</div>
|
|
989
|
+
</div>
|
|
990
|
+
</div>
|
|
991
|
+
</div>
|
|
992
|
+
|
|
993
|
+
<div class="row g-3 mt-0">
|
|
994
|
+
<!-- Source Quality -->
|
|
995
|
+
<div class="col-md-6">
|
|
996
|
+
<div class="card p-3 h-100">
|
|
997
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
998
|
+
<h6 class="mb-0"><i class="bi bi-stars"></i> Source Quality</h6>
|
|
999
|
+
<span class="badge bg-success">Per-Tool Scoring</span>
|
|
1000
|
+
</div>
|
|
1001
|
+
<div id="learning-sources">
|
|
1002
|
+
<div class="text-center text-muted py-3">
|
|
1003
|
+
<i class="bi bi-hourglass-split" style="font-size: 1.5rem;"></i>
|
|
1004
|
+
<p class="mt-2 mb-0 small">Quality scores computed after feedback signals</p>
|
|
1005
|
+
</div>
|
|
1006
|
+
</div>
|
|
1007
|
+
</div>
|
|
1008
|
+
</div>
|
|
1009
|
+
|
|
1010
|
+
<!-- Engagement & Privacy -->
|
|
1011
|
+
<div class="col-md-6">
|
|
1012
|
+
<div class="card p-3 h-100">
|
|
1013
|
+
<div class="d-flex justify-content-between align-items-center mb-3">
|
|
1014
|
+
<h6 class="mb-0"><i class="bi bi-shield-lock"></i> Privacy & Data</h6>
|
|
1015
|
+
<span class="badge bg-secondary">GDPR Compliant</span>
|
|
1016
|
+
</div>
|
|
1017
|
+
<div id="learning-privacy">
|
|
1018
|
+
<div class="small">
|
|
1019
|
+
<div class="d-flex justify-content-between border-bottom py-1">
|
|
1020
|
+
<span class="text-muted">Learning DB</span>
|
|
1021
|
+
<span class="fw-bold" id="learning-db-size">--</span>
|
|
1022
|
+
</div>
|
|
1023
|
+
<div class="d-flex justify-content-between border-bottom py-1">
|
|
1024
|
+
<span class="text-muted">Patterns learned</span>
|
|
1025
|
+
<span class="fw-bold" id="learning-pattern-count">0</span>
|
|
1026
|
+
</div>
|
|
1027
|
+
<div class="d-flex justify-content-between border-bottom py-1">
|
|
1028
|
+
<span class="text-muted">Models trained</span>
|
|
1029
|
+
<span class="fw-bold" id="learning-model-count">0</span>
|
|
1030
|
+
</div>
|
|
1031
|
+
<div class="d-flex justify-content-between border-bottom py-1">
|
|
1032
|
+
<span class="text-muted">Sources tracked</span>
|
|
1033
|
+
<span class="fw-bold" id="learning-source-count">0</span>
|
|
1034
|
+
</div>
|
|
1035
|
+
<div class="d-flex justify-content-between py-1">
|
|
1036
|
+
<span class="text-muted">Telemetry</span>
|
|
1037
|
+
<span class="text-success fw-bold">None</span>
|
|
1038
|
+
</div>
|
|
1039
|
+
</div>
|
|
1040
|
+
<div class="mt-2">
|
|
1041
|
+
<button class="btn btn-sm btn-outline-danger" onclick="resetLearning()">
|
|
1042
|
+
<i class="bi bi-trash"></i> Reset Learning Data
|
|
1043
|
+
</button>
|
|
1044
|
+
<small class="text-muted d-block mt-1">Deletes learning.db. Memories preserved.</small>
|
|
1045
|
+
</div>
|
|
1046
|
+
</div>
|
|
1047
|
+
</div>
|
|
1048
|
+
</div>
|
|
1049
|
+
</div>
|
|
1050
|
+
</div>
|
|
1051
|
+
|
|
909
1052
|
<!-- Live Events (v2.5) -->
|
|
910
1053
|
<div class="tab-pane fade" id="events-pane">
|
|
911
1054
|
<div class="card p-3 mb-3">
|
|
@@ -1175,6 +1318,7 @@
|
|
|
1175
1318
|
<script src="static/js/settings.js"></script>
|
|
1176
1319
|
<script src="static/js/events.js"></script>
|
|
1177
1320
|
<script src="static/js/agents.js"></script>
|
|
1321
|
+
<script src="static/js/learning.js"></script>
|
|
1178
1322
|
<script src="static/js/init.js"></script>
|
|
1179
1323
|
|
|
1180
1324
|
<footer>
|
package/ui/js/init.js
CHANGED
|
@@ -29,3 +29,7 @@ if (eventsTab) eventsTab.addEventListener('shown.bs.tab', loadEventStats);
|
|
|
29
29
|
|
|
30
30
|
var agentsTab = document.getElementById('agents-tab');
|
|
31
31
|
if (agentsTab) agentsTab.addEventListener('shown.bs.tab', loadAgents);
|
|
32
|
+
|
|
33
|
+
// v2.7 learning tab (graceful)
|
|
34
|
+
var learningTab = document.getElementById('learning-tab');
|
|
35
|
+
if (learningTab) learningTab.addEventListener('shown.bs.tab', loadLearning);
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
// SuperLocalMemory V2 - Learning System Dashboard (v2.7)
|
|
2
|
+
// Copyright (c) 2026 Varun Pratap Bhardwaj - MIT License
|
|
3
|
+
// NOTE: All dynamic values pass through escapeHtml() from core.js before DOM insertion.
|
|
4
|
+
|
|
5
|
+
var _learningData = null;
|
|
6
|
+
|
|
7
|
+
async function loadLearning() {
|
|
8
|
+
try {
|
|
9
|
+
var response = await fetch('/api/learning/status');
|
|
10
|
+
var data = await response.json();
|
|
11
|
+
_learningData = data;
|
|
12
|
+
renderLearningStatus(data);
|
|
13
|
+
} catch (error) {
|
|
14
|
+
console.error('Error loading learning status:', error);
|
|
15
|
+
var el = document.getElementById('learning-phase');
|
|
16
|
+
if (el) el.textContent = 'Unavailable';
|
|
17
|
+
var detail = document.getElementById('learning-phase-detail');
|
|
18
|
+
if (detail) detail.textContent = 'Learning system not available';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function renderLearningStatus(data) {
|
|
23
|
+
renderPhase(data);
|
|
24
|
+
renderFeedbackCount(data.stats);
|
|
25
|
+
renderEngagementHealth(data.engagement);
|
|
26
|
+
renderProgressBar(data.stats ? data.stats.feedback_count : 0);
|
|
27
|
+
renderTechPreferences(data.tech_preferences || []);
|
|
28
|
+
renderWorkflowPatterns(data.workflow_patterns || []);
|
|
29
|
+
renderSourceQuality(data.source_scores || {});
|
|
30
|
+
renderPrivacyStats(data.stats);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function renderPhase(data) {
|
|
34
|
+
var phaseEl = document.getElementById('learning-phase');
|
|
35
|
+
var phaseDetail = document.getElementById('learning-phase-detail');
|
|
36
|
+
if (!phaseEl) return;
|
|
37
|
+
|
|
38
|
+
if (!data.ranking_phase) {
|
|
39
|
+
phaseEl.textContent = 'Not Available';
|
|
40
|
+
phaseEl.style.color = 'var(--bs-secondary)';
|
|
41
|
+
if (phaseDetail) phaseDetail.textContent = 'Install: pip3 install lightgbm scipy';
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
var labels = { 'baseline': 'Baseline', 'rule_based': 'Rule-Based', 'ml_model': 'ML Model' };
|
|
46
|
+
var colors = { 'baseline': 'var(--bs-secondary)', 'rule_based': 'var(--bs-primary)', 'ml_model': 'var(--bs-warning)' };
|
|
47
|
+
phaseEl.textContent = labels[data.ranking_phase] || data.ranking_phase;
|
|
48
|
+
phaseEl.style.color = colors[data.ranking_phase] || 'var(--bs-primary)';
|
|
49
|
+
|
|
50
|
+
var fc = (data.stats && data.stats.feedback_count) || 0;
|
|
51
|
+
if (phaseDetail) {
|
|
52
|
+
if (data.ranking_phase === 'baseline') phaseDetail.textContent = 'Need 20+ signals. Currently: ' + fc;
|
|
53
|
+
else if (data.ranking_phase === 'rule_based') phaseDetail.textContent = 'Active! Need 200+ for ML. Currently: ' + fc;
|
|
54
|
+
else phaseDetail.textContent = 'Full ML ranking with ' + fc + ' signals';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function renderFeedbackCount(stats) {
|
|
59
|
+
var el = document.getElementById('learning-feedback-count');
|
|
60
|
+
var detail = document.getElementById('learning-feedback-detail');
|
|
61
|
+
if (el && stats) el.textContent = stats.feedback_count || 0;
|
|
62
|
+
if (detail && stats) detail.textContent = (stats.unique_queries || 0) + ' unique queries';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function renderEngagementHealth(engagement) {
|
|
66
|
+
var el = document.getElementById('learning-health');
|
|
67
|
+
var detail = document.getElementById('learning-health-detail');
|
|
68
|
+
if (!el) return;
|
|
69
|
+
if (!engagement) { el.textContent = '--'; return; }
|
|
70
|
+
|
|
71
|
+
var status = engagement.health_status || 'UNKNOWN';
|
|
72
|
+
var colors = { 'HEALTHY': 'var(--bs-success)', 'DECLINING': 'var(--bs-warning)', 'AT_RISK': 'var(--bs-danger)', 'INACTIVE': 'var(--bs-secondary)' };
|
|
73
|
+
el.textContent = status;
|
|
74
|
+
el.style.color = colors[status] || 'var(--bs-secondary)';
|
|
75
|
+
if (detail) {
|
|
76
|
+
var p = [];
|
|
77
|
+
if (engagement.days_active !== undefined) p.push(engagement.days_active + ' days active');
|
|
78
|
+
if (engagement.memories_per_day !== undefined) p.push(engagement.memories_per_day.toFixed(1) + ' mem/day');
|
|
79
|
+
detail.textContent = p.join(' | ') || 'No data';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function renderProgressBar(count) {
|
|
84
|
+
var bar = document.getElementById('learning-progress');
|
|
85
|
+
if (!bar) return;
|
|
86
|
+
var pct = 0, cls = 'bg-secondary', lbl = '';
|
|
87
|
+
|
|
88
|
+
if (count >= 200) { pct = 100; cls = 'bg-warning'; lbl = 'ML Model Active'; }
|
|
89
|
+
else if (count >= 20) { pct = 10 + ((count - 20) / 180) * 60; cls = 'bg-primary'; lbl = 'Rule-Based (' + count + '/200)'; }
|
|
90
|
+
else if (count > 0) { pct = (count / 20) * 10; lbl = 'Baseline (' + count + '/20)'; }
|
|
91
|
+
else { lbl = 'No feedback yet'; }
|
|
92
|
+
|
|
93
|
+
bar.style.width = Math.min(pct, 100) + '%';
|
|
94
|
+
bar.className = 'progress-bar ' + cls;
|
|
95
|
+
bar.textContent = lbl;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function renderTechPreferences(patterns) {
|
|
99
|
+
var container = document.getElementById('learning-tech-prefs');
|
|
100
|
+
if (!container) return;
|
|
101
|
+
container.textContent = '';
|
|
102
|
+
|
|
103
|
+
if (!patterns || patterns.length === 0) {
|
|
104
|
+
var empty = document.createElement('div');
|
|
105
|
+
empty.className = 'text-center text-muted py-3';
|
|
106
|
+
empty.textContent = 'No patterns yet. Use recall + feedback to start learning.';
|
|
107
|
+
container.appendChild(empty);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
for (var i = 0; i < patterns.length; i++) {
|
|
112
|
+
var p = patterns[i];
|
|
113
|
+
var confPct = Math.round((p.confidence || 0) * 100);
|
|
114
|
+
var barColor = confPct >= 80 ? 'bg-success' : (confPct >= 60 ? 'bg-primary' : 'bg-secondary');
|
|
115
|
+
|
|
116
|
+
var row = document.createElement('div');
|
|
117
|
+
row.className = 'd-flex align-items-center mb-2';
|
|
118
|
+
|
|
119
|
+
var label = document.createElement('div');
|
|
120
|
+
label.style.minWidth = '120px';
|
|
121
|
+
var labelSpan = document.createElement('span');
|
|
122
|
+
labelSpan.className = 'text-muted small';
|
|
123
|
+
labelSpan.textContent = p.key || '';
|
|
124
|
+
label.appendChild(labelSpan);
|
|
125
|
+
|
|
126
|
+
var barWrap = document.createElement('div');
|
|
127
|
+
barWrap.className = 'flex-grow-1 mx-2';
|
|
128
|
+
var progress = document.createElement('div');
|
|
129
|
+
progress.className = 'progress';
|
|
130
|
+
progress.style.height = '18px';
|
|
131
|
+
progress.style.borderRadius = '9px';
|
|
132
|
+
var barEl = document.createElement('div');
|
|
133
|
+
barEl.className = 'progress-bar ' + barColor;
|
|
134
|
+
barEl.style.width = confPct + '%';
|
|
135
|
+
barEl.style.borderRadius = '9px';
|
|
136
|
+
barEl.style.fontSize = '0.7rem';
|
|
137
|
+
barEl.textContent = (p.value || '') + ' (' + confPct + '%)';
|
|
138
|
+
progress.appendChild(barEl);
|
|
139
|
+
barWrap.appendChild(progress);
|
|
140
|
+
|
|
141
|
+
var evidence = document.createElement('small');
|
|
142
|
+
evidence.className = 'text-muted';
|
|
143
|
+
evidence.style.minWidth = '60px';
|
|
144
|
+
evidence.style.textAlign = 'right';
|
|
145
|
+
evidence.textContent = (p.evidence || 0) + ' ev.';
|
|
146
|
+
|
|
147
|
+
row.appendChild(label);
|
|
148
|
+
row.appendChild(barWrap);
|
|
149
|
+
row.appendChild(evidence);
|
|
150
|
+
container.appendChild(row);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderWorkflowPatterns(workflows) {
|
|
155
|
+
var container = document.getElementById('learning-workflows');
|
|
156
|
+
if (!container) return;
|
|
157
|
+
container.textContent = '';
|
|
158
|
+
|
|
159
|
+
if (!workflows || workflows.length === 0) {
|
|
160
|
+
var empty = document.createElement('div');
|
|
161
|
+
empty.className = 'text-center text-muted py-3';
|
|
162
|
+
empty.textContent = 'Sequences detected after 30+ memories.';
|
|
163
|
+
container.appendChild(empty);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for (var i = 0; i < workflows.length; i++) {
|
|
168
|
+
var w = workflows[i];
|
|
169
|
+
var card = document.createElement('div');
|
|
170
|
+
card.className = 'mb-2 p-2 border rounded';
|
|
171
|
+
|
|
172
|
+
if (w.type === 'sequence') {
|
|
173
|
+
var seq = [];
|
|
174
|
+
try { seq = JSON.parse(w.value); } catch(e) { seq = [String(w.value)]; }
|
|
175
|
+
var flowDiv = document.createElement('div');
|
|
176
|
+
flowDiv.className = 'd-flex align-items-center flex-wrap gap-1';
|
|
177
|
+
for (var j = 0; j < seq.length; j++) {
|
|
178
|
+
if (j > 0) {
|
|
179
|
+
var arrow = document.createElement('i');
|
|
180
|
+
arrow.className = 'bi bi-arrow-right text-muted small';
|
|
181
|
+
flowDiv.appendChild(arrow);
|
|
182
|
+
}
|
|
183
|
+
var badge = document.createElement('span');
|
|
184
|
+
badge.className = 'badge bg-primary bg-opacity-75';
|
|
185
|
+
badge.textContent = seq[j];
|
|
186
|
+
flowDiv.appendChild(badge);
|
|
187
|
+
}
|
|
188
|
+
card.appendChild(flowDiv);
|
|
189
|
+
} else if (w.type === 'temporal') {
|
|
190
|
+
// Parse temporal pattern: show "Morning: code (26%)" format
|
|
191
|
+
var parsed = {};
|
|
192
|
+
try { parsed = JSON.parse(w.value); } catch(e) { parsed = {}; }
|
|
193
|
+
var timeBadge = document.createElement('span');
|
|
194
|
+
timeBadge.className = 'badge bg-info bg-opacity-75 me-2';
|
|
195
|
+
timeBadge.textContent = (w.key || '').charAt(0).toUpperCase() + (w.key || '').slice(1);
|
|
196
|
+
card.appendChild(timeBadge);
|
|
197
|
+
|
|
198
|
+
var activity = document.createElement('span');
|
|
199
|
+
activity.className = 'fw-bold';
|
|
200
|
+
activity.textContent = parsed.dominant_activity || w.value;
|
|
201
|
+
card.appendChild(activity);
|
|
202
|
+
|
|
203
|
+
var evCount = document.createElement('small');
|
|
204
|
+
evCount.className = 'text-muted ms-2';
|
|
205
|
+
evCount.textContent = '(' + (parsed.evidence_count || 0) + ' memories)';
|
|
206
|
+
card.appendChild(evCount);
|
|
207
|
+
|
|
208
|
+
// Show distribution as mini bar
|
|
209
|
+
if (parsed.distribution) {
|
|
210
|
+
var distDiv = document.createElement('div');
|
|
211
|
+
distDiv.className = 'd-flex flex-wrap gap-1 mt-1';
|
|
212
|
+
var sortedActivities = Object.entries(parsed.distribution).sort(function(a, b) { return b[1] - a[1]; });
|
|
213
|
+
var total = sortedActivities.reduce(function(s, e) { return s + e[1]; }, 0);
|
|
214
|
+
for (var k = 0; k < sortedActivities.length; k++) {
|
|
215
|
+
var actName = sortedActivities[k][0];
|
|
216
|
+
var actCount = sortedActivities[k][1];
|
|
217
|
+
var actPct = Math.round((actCount / total) * 100);
|
|
218
|
+
var actBadge = document.createElement('span');
|
|
219
|
+
actBadge.className = 'badge bg-light text-dark';
|
|
220
|
+
actBadge.style.fontSize = '0.65rem';
|
|
221
|
+
actBadge.textContent = actName + ' ' + actPct + '%';
|
|
222
|
+
distDiv.appendChild(actBadge);
|
|
223
|
+
}
|
|
224
|
+
card.appendChild(distDiv);
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
var typeBadge = document.createElement('span');
|
|
228
|
+
typeBadge.className = 'badge bg-info bg-opacity-75';
|
|
229
|
+
typeBadge.textContent = w.key || w.type;
|
|
230
|
+
card.appendChild(typeBadge);
|
|
231
|
+
var valSpan = document.createElement('span');
|
|
232
|
+
valSpan.className = 'small ms-1';
|
|
233
|
+
valSpan.textContent = w.value || '';
|
|
234
|
+
card.appendChild(valSpan);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
var confSmall = document.createElement('small');
|
|
238
|
+
confSmall.className = 'text-muted d-block';
|
|
239
|
+
confSmall.textContent = 'Confidence: ' + Math.round((w.confidence || 0) * 100) + '%';
|
|
240
|
+
card.appendChild(confSmall);
|
|
241
|
+
container.appendChild(card);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function renderSourceQuality(scores) {
|
|
246
|
+
var container = document.getElementById('learning-sources');
|
|
247
|
+
if (!container) return;
|
|
248
|
+
container.textContent = '';
|
|
249
|
+
|
|
250
|
+
var sources = Object.keys(scores);
|
|
251
|
+
if (sources.length === 0) {
|
|
252
|
+
var empty = document.createElement('div');
|
|
253
|
+
empty.className = 'text-center text-muted py-3';
|
|
254
|
+
empty.textContent = 'Source quality computed after feedback signals.';
|
|
255
|
+
container.appendChild(empty);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
sources.sort(function(a, b) { return scores[b] - scores[a]; });
|
|
260
|
+
|
|
261
|
+
for (var i = 0; i < sources.length; i++) {
|
|
262
|
+
var src = sources[i];
|
|
263
|
+
var pct = Math.round(scores[src] * 100);
|
|
264
|
+
var barColor = pct >= 60 ? 'bg-success' : (pct >= 40 ? 'bg-warning' : 'bg-danger');
|
|
265
|
+
|
|
266
|
+
var row = document.createElement('div');
|
|
267
|
+
row.className = 'd-flex align-items-center mb-2';
|
|
268
|
+
|
|
269
|
+
var label = document.createElement('div');
|
|
270
|
+
label.style.minWidth = '140px';
|
|
271
|
+
var code = document.createElement('code');
|
|
272
|
+
code.className = 'small';
|
|
273
|
+
code.textContent = src;
|
|
274
|
+
label.appendChild(code);
|
|
275
|
+
|
|
276
|
+
var barWrap = document.createElement('div');
|
|
277
|
+
barWrap.className = 'flex-grow-1 mx-2';
|
|
278
|
+
var progress = document.createElement('div');
|
|
279
|
+
progress.className = 'progress';
|
|
280
|
+
progress.style.height = '16px';
|
|
281
|
+
progress.style.borderRadius = '8px';
|
|
282
|
+
var barEl = document.createElement('div');
|
|
283
|
+
barEl.className = 'progress-bar ' + barColor;
|
|
284
|
+
barEl.style.width = pct + '%';
|
|
285
|
+
barEl.style.borderRadius = '8px';
|
|
286
|
+
barEl.style.fontSize = '0.65rem';
|
|
287
|
+
barEl.textContent = pct + '%';
|
|
288
|
+
progress.appendChild(barEl);
|
|
289
|
+
barWrap.appendChild(progress);
|
|
290
|
+
|
|
291
|
+
row.appendChild(label);
|
|
292
|
+
row.appendChild(barWrap);
|
|
293
|
+
container.appendChild(row);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function renderPrivacyStats(stats) {
|
|
298
|
+
if (!stats) return;
|
|
299
|
+
var el;
|
|
300
|
+
el = document.getElementById('learning-db-size');
|
|
301
|
+
if (el) el.textContent = (stats.db_size_kb || 0) + ' KB';
|
|
302
|
+
el = document.getElementById('learning-pattern-count');
|
|
303
|
+
if (el) el.textContent = stats.transferable_patterns || 0;
|
|
304
|
+
el = document.getElementById('learning-model-count');
|
|
305
|
+
if (el) el.textContent = stats.models_trained || 0;
|
|
306
|
+
el = document.getElementById('learning-source-count');
|
|
307
|
+
if (el) el.textContent = stats.tracked_sources || 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
async function resetLearning() {
|
|
311
|
+
if (!confirm('Delete all learning data? Memories will be preserved.')) return;
|
|
312
|
+
try {
|
|
313
|
+
var response = await fetch('/api/learning/reset', { method: 'POST' });
|
|
314
|
+
var data = await response.json();
|
|
315
|
+
if (data.success) { alert('Learning data reset. Memories preserved.'); loadLearning(); }
|
|
316
|
+
else alert('Reset failed: ' + (data.error || 'Unknown error'));
|
|
317
|
+
} catch (error) { alert('Reset failed: ' + error.message); }
|
|
318
|
+
}
|
package/ui_server.py
CHANGED
|
@@ -142,6 +142,13 @@ from routes.events import router as events_router, register_event_listener
|
|
|
142
142
|
from routes.agents import router as agents_router
|
|
143
143
|
from routes.ws import router as ws_router, manager as ws_manager
|
|
144
144
|
|
|
145
|
+
# v2.7 Learning routes (graceful)
|
|
146
|
+
try:
|
|
147
|
+
from routes.learning import router as learning_router
|
|
148
|
+
LEARNING_ROUTES = True
|
|
149
|
+
except ImportError:
|
|
150
|
+
LEARNING_ROUTES = False
|
|
151
|
+
|
|
145
152
|
app.include_router(memories_router)
|
|
146
153
|
app.include_router(stats_router)
|
|
147
154
|
app.include_router(profiles_router)
|
|
@@ -150,6 +157,8 @@ app.include_router(data_io_router)
|
|
|
150
157
|
app.include_router(events_router)
|
|
151
158
|
app.include_router(agents_router)
|
|
152
159
|
app.include_router(ws_router)
|
|
160
|
+
if LEARNING_ROUTES:
|
|
161
|
+
app.include_router(learning_router)
|
|
153
162
|
|
|
154
163
|
# Wire WebSocket manager into routes that need broadcast capability
|
|
155
164
|
import routes.profiles as _profiles_mod
|